- Linux程序設計(第4版)
- (英)Neil Matthew Richard Stones
- 6466字
- 2021-04-09 20:04:38
1.2 Linux程序設計
許多人認為Linux程序設計就是用C語言編程。的確,UNIX最初是用C語言編寫的,并且UNIX的大多數應用程序也是用C語言編寫的,但C語言并不是Linux程序員或UNIX程序員的唯一選擇。在本書中,我們將介紹幾種其他的選擇。
事實上,UNIX的第一個版本是在1969年用PDP 7機器的匯編語言編寫的。差不多也在那個時候,Dennis Ritchie發明了C語言,并于1973年與Ken Thompson一起用C語言重寫了整個UNIX內核,這在那個連系統軟件都是用匯編語言編寫的時代的確是個了不起的壯舉。
對Linux系統來說,有各種各樣的編程語言可供選用,其中許多是免費的,它們可以通過CD-ROM光盤獲得或在因特網上通過FTP站點下載。表1-1列出了Linux程序員可用的部分編程語言。
表 1-1

我們將在第2章向讀者顯示如何用Linux的shell(bash)來開發小規模到中等規模的應用程序。在本書的其他章節,我們主要集中在C語言上。我們將集中精力從C語言程序員的視角探究Linux編程接口。我們假設讀者具備C語言編程的基礎。
1.2.1 Linux程序
Linux應用程序表現為兩種特殊類型的文件:可執行文件和腳本文件??蓤绦形募怯嬎銠C可以直接運行的程序,它們相當于Windows中的.exe文件。腳本文件是一組指令的集合,這些指令將由另一個程序(即解釋器)來執行,它們相當于Windows中的.bat文件、.cmd文件或解釋執行的BASIC程序。
Linux并不要求可執行文件或腳本文件具有特殊的文件名或擴展名。文件系統屬性(我們將在第2章中討論)用來指明一個文件是否為可執行的程序。在Linux中,你可以用編譯過的程序代替腳本(反之亦然)而不會影響其他程序或調用者。事實上,在用戶級別,這兩者本質上沒有任何不同。
當登錄進Linux系統時,你與一個shell程序(通常是bash)進行交互,它像Windows中的命令提示窗口一樣運行程序。它在一組指定的目錄路徑下按照你給出的程序名搜索與之同名的文件。搜索的目錄路徑存儲在shell變量PATH里,這一點與Windows也很類似。搜索路徑(你也可以添加這個路徑)由系統管理員配置,它通常包含如下一些存儲系統程序的標準路徑。
? /bin:二進制文件目錄,用于存放啟動系統時用到的程序。
? /usr/bin:用戶二進制文件目錄,用于存放用戶使用的標準程序。
? /usr/local/bin:本地二進制文件目錄,用于存放軟件安裝的程序。
系統管理員(例如root用戶)登錄后使用的PATH變量可能還包含存放系統管理程序的目錄,如/sbin和/usr/sbin。
可選的操作系統組件和第三方應用程序可能被安裝在/opt目錄下,安裝程序可以通過用戶安裝腳本將路徑添加到PATH環境變量中。
從PATH中刪除目錄并不是一個好主意,除非確信你了解這么做的后果。
注意,Linux像UNIX一樣,使用冒號(:)分隔PATH變量里的條目,而不是像MS-DOS和Windows使用分號(;)。(UNIX使用冒號:在先,所以應該問微軟為什么Windows要采用不同的方式,而不是問UNIX為什么與之不同?。┫旅媸且粋€PATH變量的例子:

上面的PATH變量包含的條目有:標準程序存放位置、當前目錄(.)、一個用戶的家目錄和X視窗系統的目錄。
記住,Linux用正斜線(/)分隔文件名里的目錄名,而不是像Windows那樣用反斜線(\)。而且這次還是UNIX的用法在先。
1.2.2 文本編輯器
編寫和輸入本書中的代碼需要使用一個編輯器。在典型的Linux系統上有許多編輯器可用,較流行的編輯器是vi。
本書的兩位作者都喜歡Emacs,所以我們建議你花一點時間來學習這個功能強大的編輯器。幾乎所有的Linux發行版都將Emacs作為可選的安裝包,你也可以從GNU網站http://www.gnu.org上得到它,或者在XEmacs網站http://www.xemacs.org上得到它的圖形化環境版本。
要更深入地學習Emacs,你可以使用它的在線指南。首先運行emacs命令以啟動編輯器,然后輸入Ctrl+H,接著輸入字母t就進入了在線指南。Emacs也有完整的用戶手冊。在Emacs中輸入Ctrl+H,接著輸入字母i即可以得到相關信息。有些版本的Emacs可能包含訪問手冊和指南的菜單。
1.2.3 C語言編譯器
在POSIX兼容的系統中,C語言編譯器被稱為c89。歷史上,C語言編譯器被簡稱為cc。許多年來,不同廠商銷售的類UNIX系統中所帶的C語言編譯器均包含不同的功能和選項,但它們一般都稱為cc。
在準備起草POSIX標準時,事實上已經不可能制訂出兼容所有廠商的標準cc命令了。于是,POSIX委員會決定為C語言編譯器創建新的標準命令,這就是c89。只要使用這個命令,在任何機器上,它的編譯選項都相同。
Linux系統盡量實現這些標準。在Linux系統中,你會發現c89、cc和gcc這些命令全部或部分地指向系統的C語言編譯器,通常是GNU C編譯器,或gcc。在UNIX系統中,C語言編譯器幾乎總被稱為cc。
在本書中,我們將使用gcc,這是因為它隨Linux的發行版一起提供,并且它支持C語言的ANSI標準語法。如果你發現你的UNIX系統中沒有gcc,我們建議你設法獲取并安裝它。你可以在http://www.gnu.org上找到它。我們在本書中用到gcc之處,你都可以直接將其替換為你的系統中C語言編譯器相應的命令。
實驗 你的第一個Linux C語言程序
在本例中,通過編寫、編譯和運行你的第一個Linux程序來開始Linux的C語言程序開發之旅。還是從最有名的Hello World程序開始吧。
(1)下面是文件hello.c的源代碼:

(2)編譯、鏈接和運行程序。

實驗解析
你調用GNU C語言編譯器(在Linux中,大多數情況下用cc也可以)將C語言源代碼轉換為可執行文件hello。然后運行這個程序,它將打印出歡迎信息。雖然這只是最簡單的一個例子,但如果在你的系統上能做到這一點,你就能編譯、運行本書中以后所有的例子了。如果無法完成上述操作,請檢查你的系統以確保已安裝了C語言編譯器。例如,許多Linux發行版有個名為Software Development(軟件開發)的安裝選項(或類似選項),你應該在Linux系統安裝過程中選中該項,從而確保安裝了所需的軟件包。
因為這是你運行的第一個程序,所以有些問題最好現在就指出來。hello程序很可能在你的家目錄中。如果PATH變量不包含指向你的家目錄的條目,shell就找不到hello程序。更進一步,如果PATH變量中包含的其中一個目錄包含另一個名為hello的程序,shell就會執行那個程序。如果PATH中這樣的目錄出現在你的家目錄之前,這種情況也會發生。為了避免這種潛在的問題,你可以在程序名前加上一個./(例如./hello)。它特別指示shell去執行當前目錄下給定名稱的程序。(符號.代表當前目錄。)
如果你忘記用-o name選項告訴編譯器可執行程序的名字,編譯器就會把程序放在一個名為a.out的文件里(a.out的含義是assembler output,即匯編輸出)。如果你確信編譯了一個程序但又找不到它,別忘了看看有沒有a.out文件!在UNIX的早期歷史中,想在系統上玩游戲的人通常把游戲作為a.out來運行,以避免被系統管理員捉到,因此一些UNIX系統每晚會定期地刪除所有名為a.out的文件。
1.2.4 開發系統導引
對Linux開發人員來說,了解軟件工具和開發資源在系統中存放的位置是很重要的。以下幾節將簡單介紹一些重要的目錄和文件。
1.應用程序
應用程序通常存放在系統為之保留的特定目錄中。系統為正常使用提供的程序,包括用于程序開發的工具,都可在目錄/usr/bin中找到;系統管理員為某個特定的主機或本地網絡添加的程序通??稍谀夸?usr/local/bin或/opt中找到。
系統管理員一般喜歡使用/opt和/usr/local目錄,因為它們分離了廠商提供及后續添加的文件與系統本身提供的應用程序。一直保持以這種方式組織文件的好處在你需要升級操作系統時就可以看出來了,因為只有目錄/opt和/usr/local里的內容需要保留。我們建議對于系統級的應用程序,你可以將它放在/usr/local目錄中來運行和訪問所需的文件。對于開發用和個人的應用程序,最好在你的家目錄中使用一個文件夾來存放它。
其他一些功能特性和編程系統可能有其自己的目錄結構和程序目錄。其中最主要的一個就是X視窗系統,它通常安裝在/usr/X11或/usr/bin/X11目錄中。Linux發行版通常使用X視窗系統的X.Org基金會版本,它基于修訂版7(X11R7)。其他類UNIX系統可能選擇X視窗系統的其他版本,它們被安裝到不同的位置,如Solaris提供的Sun Open Windows被安裝到/usr/openwin目錄中。
GNU編譯系統的驅動程序gcc(你已在本章前面的編程示例中用過)一般位于/usr/bin或/usr/local/bin目錄中,但它會從其他位置運行各種編譯器支持的應用程序。這個位置是在編譯編譯器本身時指定的,并且它隨主機類型的不同而不同。對Linux系統來說,這個位置可能是/usr/lib/gcc/目錄下的一個版本特定的子目錄。在撰寫本書時,這個目錄在本書其中一位作者的機器上是/usr/lib/gcc/i586-suse-linux/4.1.3。GNU C/C++編譯器的各個工具和GNU專用的頭文件都保存在這里。
2.頭文件
用C語言及其他語言進行程序設計時,你需要用頭文件來提供對常量的定義和對系統函數及庫函數調用的聲明。對C語言來說,這些頭文件幾乎總是位于/usr/include目錄及其子目錄中。那些依賴于特定Linux版本的頭文件通常可在目錄/usr/include/sys和/usr/include/linux中找到。
其他編程系統也有各自的頭文件,這些頭文件被存儲在可被相應編譯器自動搜索到的目錄里。例如,X視窗系統的/usr/include/X11目錄和GNU C++的/usr/include/c++目錄。
在調用C語言編譯器時,你可以使用-I標志來包含保存在子目錄或非標準位置中的頭文件。例如:

它指示編譯器不僅在標準位置,也在/usr/openwin/include目錄中查找程序fred.c中包含的頭文件。請參看C語言編譯器的使用手冊(man gcc)以了解更多細節。
用grep命令來搜索包含某些特定定義和函數原型的頭文件是很方便的。假設想知道用于從程序中返回退出狀態的#define定義的名字,你只需切換到/usr/include目錄下,然后用grep命令搜索可能的名字部分,如下所示:

上面的grep命令在當前目錄下的所有以.h結尾的文件中搜索字符串EXIT_。在本例中,它在stdlib.h文件中找到了你需要的定義。
3.庫文件
庫是一組預先編譯好的函數的集合,這些函數都是按照可重用的原則編寫的。它們通常由一組相互關聯的函數組成以執行某項常見的任務,比如屏幕處理函數庫(curses和ncurses庫)和數據庫訪問例程(dbm庫)。我們將在后續的章節中介紹一些函數庫。
標準系統庫文件一般存儲在/lib和/usr/lib目錄中。C語言編譯器(或更確切地說是鏈接程序)需要知道要搜索哪些庫文件,因為在默認情況下,它只搜索標準C語言庫。這是從那個計算機速度還很慢而且CPU運行周期還很昂貴的時代遺留下來的問題。僅把庫文件放在標準目錄中,就希望編譯器能夠找到它是不夠的,庫文件必須遵循特定的命名規范并且需要在命令行中明確指定。
庫文件的名字總是以lib開頭,隨后的部分指明這是什么庫(例如,c代表C語言庫,m代表數學庫)。文件名的最后部分以.開始,然后給出庫文件的類型:
? .a代表傳統的靜態函數庫;
? .so代表共享函數庫(見后面的解釋)。
函數庫通常同時以靜態庫和共享庫兩種格式存在,你可用ls /usr/lib命令查看。你可以通過給出完整的庫文件路徑名或用-l標志來告訴編譯器要搜索的庫文件。例如:

這條命令要求編譯器編譯文件fred.c,將編譯產生的程序文件命名為fred,并且除了搜索標準的C語言函數庫外,還搜索數學庫以解決函數引用問題。下面的命令也能產生類似的結果:

-lm(在字母l和m之間沒有空格)是簡寫方式(簡寫在UNIX環境里很有用),它代表的是標準庫目錄(本例中是/usr/lib)中名為libm.a的函數庫。-lm標志的另一個好處是如果有共享庫,編譯器會自動選擇共享庫。
雖然庫文件和頭文件一樣,通常都保存在標準位置,但你也可以通過使用-L(大寫字母)標志為編譯器增加庫的搜索路徑。例如:

這條命令用/usr/openwin/lib目錄中的libX11庫版本來編譯和鏈接程序x11fred。
4.靜態庫
函數庫最簡單的形式是一組處于“準備好使用”狀態的目標文件。當程序需要使用函數庫中的某個函數時,它包含一個聲明該函數的頭文件。編譯器和鏈接器負責將程序代碼和函數庫結合在一起以組成一個單獨的可執行文件。你必須使用-l選項指明除標準C語言運行庫外還需使用的庫。
靜態庫,也稱作歸檔文件(archive),按慣例它們的文件名都以.a結尾。比如,標準C語言函數庫/usr/lib/libc.a和X11函數庫/usr/lib/libX11.a。
你可以很容易地創建和維護自己的靜態庫,只要使用ar(代表archive,即建立歸檔文件)程序和使用gcc -c命令對函數分別進行編譯。你應該盡可能把函數分別保存到不同的源文件中。如果函數需要訪問公共數據,你可以把它們放在同一個源文件中,并使用在該文件中聲明的靜態變量。
實驗 靜態庫
在本例中,你將創建一個小型函數庫,它包含兩個函數,然后你將在一個示例程序中調用其中一個函數。這兩個函數分別是fred和bill,它們只打印歡迎信息。
(1)首先,為兩個函數分別創建各自的源文件(將它們分別命名為fred.c和bill.c)。下面是第一個源文件:

下面是第二個源文件:

(2)你可以分別編譯這些函數以產生要包含在庫文件中的目標文件。這可以通過調用帶有-c選項的C語言編譯器來完成,-c選項的作用是阻止編譯器創建一個完整的程序。如果此時試圖創建一個完整的程序將不會成功,因為你還未定義main函數。

(3)現在編寫一個調用bill函數的程序。首先,為你的庫文件創建一個頭文件。這個頭文件將聲明你的庫文件中的函數,它應該被所有希望使用你的庫文件的應用程序所包含。把這個頭文件包含在源文件fred.c和bill.c中是一個好主意,它將幫助編譯器發現所有錯誤。


(4)調用程序(program.c)非常簡單。它包含庫的頭文件并且調用庫中的一個函數。

(5)現在,你可以編譯并測試這個程序了。你暫時為編譯器顯式指定目標文件,然后要求編譯器編譯你的文件并將其與先前編譯好的目標模塊bill.o鏈接。

(6)現在,你將創建并使用一個庫文件。你使用ar程序創建一個歸檔文件并將你的目標文件添加進去。這個程序之所以稱為ar,是因為它將若干單獨的文件歸并到一個大的文件中以創建歸檔文件或集合。注意,你也可以用ar程序來創建任何類型文件的歸檔文件(與許多UNIX工具一樣,ar是一個通用工具)。

(7)庫文件創建好了,兩個目標文件也已添加進去。在某些系統,尤其是從Berkeley UNIX衍生的系統中,要想成功地使用函數庫,你還需要為函數庫生成一個內容表。你可以通過ranlib命令來完成這一工作。在Linux中,當你使用的是GNU的軟件開發工具時,這一步驟并不是必需的(但做了也無妨)。

你的函數庫現在可以使用了。你可以在編譯器使用的文件列表中添加該庫文件以創建你的程序,如下所示:

你也可以使用-l選項來訪問函數庫,但因其未保存在標準位置,所以你必須使用-L選項來告訴編譯器在何處可以找到它,如下所示:
-L.選項告訴編譯器在當前目錄(.)中查找函數庫。-lfoo選項告訴編譯器使用名為libfoo.a的函數庫(或者名為libfoo.so的共享庫,如果它存在的話)。要查看哪些函數被包含在目標文件、函數庫或可執行文件里,你可以使用nm命令。如果你查看program和libfoo.a,你就會看到函數庫libfoo.a中包含fred和bill兩個函數,而program里只包含函數bill。當程序被創建時,它只包含函數庫中它實際需要的函數。雖然程序中的頭文件包含函數庫中所有函數的聲明,但這并不會將整個函數庫包含在最終的程序中。
如果你熟悉Windows軟件開發,就會發現兩者之間有許多相似之處,如表1-2所示。
表1-2

5.共享庫
靜態庫的一個缺點是,當你同時運行許多應用程序并且它們都使用來自同一個函數庫的函數時,內存中就會有同一函數的多份副本,而且在程序文件自身中也有多份同樣的副本。這將消耗大量寶貴的內存和磁盤空間。
許多支持共享庫的UNIX系統和Linux可以克服上述不足。對共享庫及其在不同系統上實現方式的詳細討論超出了本書的范圍,所以我們將僅討論Linux下的實現。
共享庫的保存位置與靜態庫是一樣的,但共享庫有不同的文件名后綴。在一個典型的Linux系統中,標準數學庫的共享版本是/usr/lib/libm.so。
當一個程序使用共享庫時,它的鏈接方式是這樣的:程序本身不再包含函數代碼,而是引用運行時可訪問的共享代碼。當編譯好的程序被裝載到內存中執行時,函數引用被解析并產生對共享庫的調用,如果有必要,共享庫才被加載到內存中。
通過這種方法,系統可以只保留共享庫的一份副本供許多應用程序同時使用,并且在磁盤上也僅保存一份。另一個好處是共享庫的更新可以獨立于依賴它的應用程序。例如,文件/lib/libm.so就是對實際庫文件修訂版本(/lib/libm.so.N,其中N代表主版本號,在寫作本書時它是6)的符號鏈接。當Linux啟動應用程序時,它會考慮應用程序需要的函數庫版本,以防止函數庫的新版本致使舊的應用程序不能使用。
下面例子的輸出取自SUSE 10.3發行版。如果你使用的不是這個發行版,輸出可能略有不同。
對Linux系統來說,負責裝載共享庫并解析客戶程序函數引用的程序(動態裝載器)是ld.so,也可能是ld-linux.so.2、ld-lsb.so.2或ld-lsb.so.3。用于搜索共享庫的額外位置可以在文件/etc/ld.so.conf中配置,如果修改了這個文件,你需要執行命令ldconfig來處理它(例如,安裝了X視窗系統后需要添加X11共享庫)。
你可以通過運行工具ldd來查看一個程序需要的共享庫。例如,如果你在自己的示例程序上運行ldd,你將看到如下所示的輸出結果:

在本例中,你看到標準C語言函數庫(libc)是共享的(.so)。程序需要的主版本號是6。其他UNIX系統在訪問共享庫時也會有類似的安排,詳情請參考你的系統文檔。
共享庫在許多方面類似于Windows中使用的動態鏈接庫。.so庫對應于.DLL文件,它們都是在程序運行時加載,而.a庫類似于.LIB文件,它們都包含在可執行程序中。
- Deploying Node.js
- 數據庫程序員面試筆試真題與解析
- Rust實戰
- x86匯編語言:從實模式到保護模式(第2版)
- 深入淺出Android Jetpack
- Hands-On Reinforcement Learning with Python
- Integrating Facebook iOS SDK with Your Application
- Hands-On Kubernetes on Windows
- Clojure for Java Developers
- Web Developer's Reference Guide
- Arduino電子設計實戰指南:零基礎篇
- Qt 4開發實踐
- Python編程快速上手2
- C語言程序設計
- HTML5程序開發范例寶典