鍍金池/ 教程/ Linux/ 編譯程序
網(wǎng)絡(luò)系統(tǒng)
打印
重定向
使用命令
位置參數(shù)
權(quán)限
文本處理
疑難排解
layout: book-zh title: 自定制 shell 提示符
查找文件
layout: book-zh title: vi 簡介
shell 環(huán)境
什么是 shell
編譯程序
鍵盤高級操作技巧
流程控制:case 分支
流程控制:if 分支結(jié)構(gòu)
layout: book-zh title: 軟件包管理
進程
存儲媒介
格式化輸出
編寫第一個 Shell 腳本
啟動一個項目
流程控制:while/until 循環(huán)
文件系統(tǒng)中跳轉(zhuǎn)
字符串和數(shù)字
讀取鍵盤輸入
歸檔和備份
探究操作系統(tǒng)
流程控制:for 循環(huán)
自頂向下設(shè)計
數(shù)組
操作文件和目錄
奇珍異寶
從 shell 眼中看世界
正則表達式

編譯程序

在這一章中,我們將看一下如何通過編譯源代碼來創(chuàng)建程序。源代碼的可用性是至關(guān)重要的自由,從而使得 Linux 成為可能。 整個 Linux 開發(fā)生態(tài)圈就是依賴于開發(fā)者之間的自由交流。對于許多桌面用戶來說,編譯是一種失傳的藝術(shù)。以前很常見, 但現(xiàn)在,由系統(tǒng)發(fā)行版提供商維護巨大的預(yù)編譯的二進制倉庫,準(zhǔn)備供用戶下載和使用。在寫這篇文章的時候, Debian 倉庫(最大的發(fā)行版之一)包含了幾乎23,000個預(yù)編譯的包。

那么為什么要編譯軟件呢? 有兩個原因:

  1. 可用性。盡管系統(tǒng)發(fā)行版?zhèn)}庫中已經(jīng)包含了大量的預(yù)編譯程序,但是一些發(fā)行版本不可能包含所有期望的應(yīng)用。 在這種情況下,得到所期望程序的唯一方式是編譯程序源碼。

  2. 及時性。雖然一些系統(tǒng)發(fā)行版專門打包前沿版本的應(yīng)用程序,但是很多不是。這意味著, 為了擁有一個最新版本的程序,編譯是必需的。

從源碼編譯軟件可以變得非常復(fù)雜且具有技術(shù)性;許多用戶難以企及。然而,許多編譯任務(wù)是 相當(dāng)簡單的,只涉及到幾個步驟。這都取決于程序包。我們將看一個非常簡單的案例, 為的是給大家提供一個對編譯過程的整體認識,并為那些愿意進一步學(xué)習(xí)的人們構(gòu)筑一個起點。

我們將介紹一個新命令:

  • make - 維護程序的工具

什么是編譯?

簡而言之,編譯就是把源碼(一個由程序員編寫的人類可讀的程序描述)翻譯成計算機處理器的母語的過程。

計算機處理器(或 CPU)工作在一個非?;镜乃?,執(zhí)行用機器語言編寫的程序。這是一種數(shù)值編碼,描述非常小的操作, 比如“加這個字節(jié)”,“指向內(nèi)存中的這個位置”,或者“復(fù)制這個字節(jié)”。

這些指令中的每一條都是用二進制表示的(1和0)。最早的計算機程序就是用這種數(shù)值編碼寫成的,這可能就 解釋了為什么編寫它們的程序員據(jù)說吸很多煙,喝大量咖啡,并帶著厚厚的眼鏡。這個問題克服了,隨著匯編語言的出現(xiàn), 匯編語言代替了數(shù)值編碼(略微)簡便地使用助記符,比如 CPY(復(fù)制)和 MOV(移動)。用匯編語言編寫的程序通過 匯編器處理為機器語言。今天為了完成某些特定的程序任務(wù),匯編語言仍在被使用,例如設(shè)備驅(qū)動和嵌入式系統(tǒng)。

下一步我們談?wù)撘幌率裁词撬^的高級編程語言。之所以這樣稱呼它們,是因為它們可以讓程序員少操心處理器的 一舉一動,而更多關(guān)心如何解決手頭的問題。早期的高級語言(二十世紀(jì)60年代期間研發(fā)的)包括 FORTRAN(為科學(xué)和技術(shù)問題而設(shè)計)和 COBOL(為商業(yè)應(yīng)用而設(shè)計)。今天這兩種語言仍在有限的使用。

雖然有許多流行的編程語言,兩個占主導(dǎo)地位。大多數(shù)為現(xiàn)代系統(tǒng)編寫的程序,要么用 C 編寫,要么是用 C++ 編寫。 在隨后的例子中,我們將編寫一個 C 程序。

用高級語言編寫的程序,經(jīng)過另一個稱為編譯器的程序的處理,會轉(zhuǎn)換成機器語言。一些編譯器把 高級指令翻譯成匯編語言,然后使用一個匯編器完成翻譯成機器語言的最后階段。

一個稱為鏈接的過程經(jīng)常與編譯結(jié)合在一起。有許多程序執(zhí)行的常見任務(wù)。以打開文件為例。許多程序執(zhí)行這個任務(wù), 但是讓每個程序?qū)崿F(xiàn)它自己的打開文件功能,是很浪費資源的。更有意義的是,擁有單獨的一段知道如何打開文件的程序, 并允許所有需要它的程序共享它。對常見任務(wù)提供支持由所謂的庫完成。這些庫包含多個程序,每個程序執(zhí)行 一些可以由多個程序共享的常見任務(wù)。如果我們看一下 /lib 和 /usr/lib 目錄,我們可以看到許多庫定居在那里。 一個叫做鏈接器的程序用來在編譯器的輸出結(jié)果和要編譯的程序所需的庫之間建立連接。這個過程的最終結(jié)果是 一個可執(zhí)行程序文件,準(zhǔn)備使用。

所有的程序都是可編譯的嗎?

不是。正如我們所看到的,有些程序比如 shell 腳本就不需要編譯。它們直接執(zhí)行。 這些程序是用所謂的腳本或解釋型語言編寫的。近年來,這些語言變得越來越流行,包括 Perl, Python,PHP,Ruby,和許多其它語言。

腳本語言由一個叫做解釋器的特殊程序執(zhí)行。一個解釋器輸入程序文件,讀取并執(zhí)行程序中包含的每一條指令。 通常來說,解釋型程序執(zhí)行起來要比編譯程序慢很多。這是因為每次解釋型程序執(zhí)行時,程序中每一條源碼指令都需要翻譯, 而一個編譯程序,一條源碼指令只翻譯一次,翻譯后的指令會永久地記錄到最終的執(zhí)行文件中。

那么為什么解釋型程序這樣流行呢?對于許多編程任務(wù)來說,原因是“足夠快”,但是真正的優(yōu)勢是一般來說開發(fā)解釋型程序 要比編譯程序快速且容易。通常程序開發(fā)需要經(jīng)歷一個不斷重復(fù)的寫碼,編譯,測試周期。隨著程序變得越來越大, 編譯階段會變得相當(dāng)耗時。解釋型語言刪除了編譯步驟,這樣就加快了程序開發(fā)。

編譯一個 C 語言

讓我們編譯一些東西。在我們行動之前,然而我們需要一些工具,像編譯器,鏈接器,還有 make。 在 Linux 環(huán)境中,普遍使用的 C 編譯器叫做 gcc(GNU C 編譯器),最初由 Richard Stallman 寫出來的。 大多數(shù) Linux 系統(tǒng)發(fā)行版默認不安裝 gcc。我們可以這樣查看該編譯器是否存在:

[me@linuxbox ~]$ which gcc
/usr/bin/gcc

在這個例子中的輸出結(jié)果表明安裝了 gcc 編譯器。


小提示: 你的系統(tǒng)發(fā)行版可能有一個用于軟件開發(fā)的 meta-package(軟件包的集合)。如果是這樣的話, 考慮安裝它,若你打算在你的系統(tǒng)中編譯程序。若你的系統(tǒng)沒有提供一個 meta-package,試著安裝 gcc 和 make 工具包。 在許多發(fā)行版中,這就足夠完成下面的練習(xí)了。

得到源碼

為了我們的編譯練習(xí),我們將編譯一個叫做 diction 的程序,來自 GNU 項目。這是一個小巧方便的程序, 檢查文本文件的書寫質(zhì)量和樣式。就程序而言,它相當(dāng)小,且容易創(chuàng)建。

遵照慣例,首先我們要創(chuàng)建一個名為 src 的目錄來存放我們的源碼,然后使用 ftp 協(xié)議把源碼下載下來。

[me@linuxbox ~]$ mkdir src
[me@linuxbox ~]$ cd src
[me@linuxbox src]$ ftp ftp.gnu.org
Connected to ftp.gnu.org.
220 GNU FTP server ready.
Name (ftp.gnu.org:me): anonymous
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> cd gnu/diction
250 Directory successfully changed.
ftp> ls
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
-rw-r--r-- 1 1003 65534 68940 Aug 28 1998 diction-0.7.tar.gz
-rw-r--r-- 1 1003 65534 90957 Mar 04 2002 diction-1.02.tar.gz
-rw-r--r-- 1 1003 65534 141062 Sep 17 2007 diction-1.11.tar.gz
226 Directory send OK.
ftp> get diction-1.11.tar.gz
local: diction-1.11.tar.gz remote: diction-1.11.tar.gz
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for diction-1.11.tar.gz
(141062 bytes).
226 File send OK.
141062 bytes received in 0.16 secs (847.4 kB/s)
ftp> bye
221 Goodbye.
[me@linuxbox src]$ ls
diction-1.11.tar.gz

注意:因為我們是這個源碼的“維護者”,當(dāng)我們編譯它的時候,我們把它保存在 ~/src 目錄下。 由你的系統(tǒng)發(fā)行版源碼會把源碼安裝在 /usr/src 目錄下,而供多個用戶使用的源碼,通常安裝在 /usr/local/src 目錄下。


正如我們所看到的,通常提供的源碼形式是一個壓縮的 tar 文件。有時候稱為 tarball,這個文件包含源碼樹, 或者是組成源碼的目錄和文件的層次結(jié)構(gòu)。當(dāng)?shù)竭_ ftp 站點之后,我們檢查可用的 tar 文件列表,然后選擇最新版本,下載。 使用 ftp 中的 get 命令,我們把文件從 ftp 服務(wù)器復(fù)制到本地機器。

一旦 tar 文件下載下來之后,必須打開。通過 tar 程序可以完成:

[me@linuxbox src]$ tar xzf diction-1.11.tar.gz
[me@linuxbox src]$ ls
diction-1.11
diction-1.11.tar.gz

小提示:該 diction 程序,像所有的 GNU 項目軟件,遵循著一定的源碼打包標(biāo)準(zhǔn)。其它大多數(shù)在 Linux 生態(tài)系統(tǒng)中 可用的源碼也遵循這個標(biāo)準(zhǔn)。該標(biāo)準(zhǔn)的一個條目是,當(dāng)源碼 tar 文件打開的時候,會創(chuàng)建一個目錄,該目錄包含了源碼樹, 并且這個目錄將會命名為 project-x.xx,其包含了項目名稱和它的版本號兩項內(nèi)容。這種方案能在系統(tǒng)中方便安裝同一程序的多個版本。 然而,通常在打開 tarball 之前檢驗源碼樹的布局是個不錯的主意。一些項目不會創(chuàng)建該目錄,反而,會把文件直接傳遞給當(dāng)前目錄。 這會把你的(除非組織良好的)src 目錄弄得一片狼藉。為了避免這個,使用下面的命令,檢查 tar 文件的內(nèi)容:

tar tzvf tarfile | head

檢查源碼樹

打開該 tar 文件,會創(chuàng)建一個新的目錄,名為 diction-1.11。這個目錄包含了源碼樹。讓我們看一下里面的內(nèi)容:

[me@linuxbox src]$ cd diction-1.11
[me@linuxbox diction-1.11]$ ls
config.guess     diction.c          getopt.c      nl
config.h.in      diction.pot        getopt.h      nl.po
config.sub       diction.spec       getopt_int.h  README
configure        diction.spec.in    INSTALL       sentence.c
configure.in     diction.texi.in    install-sh    sentence.h
COPYING en       Makefile.in        style.1.in
de               en_GB              misc.c        style.c
de.po            en_GB.po           misc.h        test
diction.1.in     getopt1.c          NEWS

在源碼樹中,我們看到大量的文件。屬于 GNU 項目的程序,還有其它許多程序都會,提供文檔文件 README,INSTALL,NEWS,和 COPYING。

這些文件包含了程序描述,如何建立和安裝它的信息,還有它許可條款。在試圖建立程序之前,仔細閱讀 README 和 INSTALL 文件,總是一個不錯的主意。

在這個目錄中,其它有趣的文件是那些以 .c 和 .h 為后綴的文件:

[me@linuxbox diction-1.11]$ ls *.c
diction.c getopt1.c getopt.c misc.c sentence.c style.c
[me@linuxbox diction-1.11]$ ls *.h
getopt.h getopt_int.h misc.h sentence.h

這些 .c 文件包含了由該軟件包提供的兩個 C 程序(style 和 diction),被分割成模塊。這是一種常見做法,把大型程序 分解成更小,更容易管理的代碼塊。源碼文件都是普通文本,可以用 less 命令查看:

[me@linuxbox diction-1.11]$ less diction.c

這些 .h 文件以頭文件而著稱。它們也是普通文件。頭文件包含了程序的描述,這些程序被包括在源碼文件或庫中。 為了讓編譯器鏈接到模塊,編譯器必須接受所需的所有模塊的描述,來完成整個程序。在 diction.c 文件的開頭附近, 我們看到這行代碼:

#include "getopt.h"

這行代碼指示編譯器去讀取文件 getopt.h,因為它會讀取 diction.c 中的源碼,為的是“知道” getopt.c 中的內(nèi)容。 getopt.c 文件提供由 style 和 diction 兩個程序共享的代碼。

在 getopt.h 的 include 語句上面,我們看到一些其它的 include 語句,比如這些:

#include <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

這些也涉及到頭文件,但是這些頭文件居住在當(dāng)前源碼樹的外面。它們由操作系統(tǒng)供給,來支持每個程序的編譯。 如果我們看一下 /usr/include 目錄,能看到它們:

[me@linuxbox diction-1.11]$ ls /usr/include

當(dāng)我們安裝編譯器的時候,這個目錄中的頭文件會被安裝。

構(gòu)建程序

大多數(shù)程序通過一個簡單的,兩個命令的序列構(gòu)建:

./configure
make

這個 configure 程序是一個 shell 腳本,由源碼樹提供。它的工作是分析程序建立環(huán)境。大多數(shù)源碼會設(shè)計為可移植的。 也就是說,它被設(shè)計成,能建立在多于一個的類 Unix 系統(tǒng)中。但是為了做到這一點,在建立程序期間,為了適應(yīng)系統(tǒng)之間的差異, 源碼可能需要經(jīng)過輕微的調(diào)整。configure 也會檢查是否安裝了必要的外部工具和組件。讓我們運行 configure 命令。 因為 configure 命令所在的位置不是位于 shell 通常期望程序所呆的地方,我們必須明確地告訴 shell 它的位置,通過 在命令之前加上 ./ 字符,來表明程序位于當(dāng)前工作目錄:

[me@linuxbox diction-1.11]$ ./configure

configure 將會輸出許多信息,隨著它測試和配置整個構(gòu)建過程。當(dāng)結(jié)束后,輸出結(jié)果看起來像這樣:

checking libintl.h presence... yes
checking for libintl.h... yes
checking for library containing gettext... none required
configure: creating ./config.status
config.status: creating Makefile
config.status: creating diction.1
config.status: creating diction.texi
config.status: creating diction.spec
config.status: creating style.1
config.status: creating test/rundiction
config.status: creating config.h
[me@linuxbox diction-1.11]$

這里最重要的事情是沒有錯誤信息。如果有錯誤信息,整個配置過程失敗,然后程序不能構(gòu)建直到修正了錯誤。

我們看到在我們的源碼目錄中 configure 命令創(chuàng)建了幾個新文件。最重要一個是 Makefile。Makefile 是一個配置文件, 指示 make 程序究竟如何構(gòu)建程序。沒有它,make 程序就不能運行。Makefile 是一個普通文本文件,所以我們能查看它:

[me@linuxbox diction-1.11]$ less Makefile

這個 make 程序把一個 makefile 文件作為輸入(通常命名為 Makefile),makefile 文件 描述了包括最終完成的程序的各組件之間的關(guān)系和依賴性。

makefile 文件的第一部分定義了變量,這些變量在該 makefile 后續(xù)章節(jié)中會被替換掉。例如我們看看這一行代碼:

CC=                 gcc

其定義了所用的 C 編譯器是 gcc。文件后面部分,我們看到一個使用該變量的實例:

diction:        diction.o sentence.o misc.o getopt.o getopt1.o
                $(CC) -o $@ $(LDFLAGS) diction.o sentence.o misc.o \
                getopt.o getopt1.o $(LIBS)

這里完成了一個替換操作,在程序運行時,$(CC) 的值會被替換成 gcc。大多數(shù) makefile 文件由行組成,每行定義一個目標(biāo)文件, 在這種情況下,目標(biāo)文件是指可執(zhí)行文件 diction,還有目標(biāo)文件所依賴的文件。剩下的行描述了從目標(biāo)文件的依賴組件中 創(chuàng)建目標(biāo)文件所需的命令。在這個例子中,我們看到可執(zhí)行文件 diction(最終的成品之一)依賴于文件 diction.o,sentence.o,misc.o,getopt.o,和 getopt1.o都存在。在 makefile 文件后面部分,我們看到 diction 文件所依賴的每一個文件做為目標(biāo)文件的定義:

diction.o:       diction.c config.h getopt.h misc.h sentence.h
getopt.o:        getopt.c getopt.h getopt_int.h
getopt1.o:       getopt1.c getopt.h getopt_int.h
misc.o:          misc.c config.h misc.h
sentence.o:      sentence.c config.h misc.h sentence.h
style.o:         style.c config.h getopt.h misc.h sentence.h

然而,我們不會看到針對它們的任何命令。這個由一個通用目標(biāo)解決,在文件的前面,描述了這個命令,用來把任意的 .c 文件編譯成 .o 文件:

.c.o:
            $(CC) -c $(CPPFLAGS) $(CFLAGS) $<

這些看起來非常復(fù)雜。為什么不簡單地列出所有的步驟,編譯完成每一部分?一會兒就知道答案了。同時, 讓我們運行 make 命令并構(gòu)建我們的程序:

[me@linuxbox diction-1.11]$ make

這個 make 程序?qū)\行,使用 Makefile 文件的內(nèi)容來指導(dǎo)它的行為。它會產(chǎn)生很多信息。

當(dāng) make 程序運行結(jié)束后,現(xiàn)在我們將看到所有的目標(biāo)文件出現(xiàn)在我們的目錄中。

[me@linuxbox diction-1.11]$ ls
config.guess  de.po             en              en_GB           sentence.c
config.h      diction           en_GB.mo        en_GB.po        sentence.h
config.h.in   diction.1         getopt1.c       getopt1.o       sentence.o
config.log    diction.1.in      getopt.c        getopt.h        style
config.status diction.c         getopt_int.h    getopt.o        style.1
config.sub    diction.o         INSTALL         install-sh      style.1.in
configure     diction.pot       Makefile        Makefile.in     style.c
configure.in  diction.spec      misc.c          misc.h          style.o
COPYING       diction.spec.in   misc.o          NEWS            test
de            diction.texi      nl              nl.mo
de.mo         diction.texi.i    nl.po           README

在這些文件之中,我們看到 diction 和 style,我們開始要構(gòu)建的程序。恭喜一切正常!我們剛才源碼編譯了 我們的第一個程序。但是出于好奇,讓我們再運行一次 make 程序:

[me@linuxbox diction-1.11]$ make
make: Nothing to be done for `all'.

它只是產(chǎn)生這樣一條奇怪的信息。怎么了?為什么它沒有重新構(gòu)建程序呢?啊,這就是 make 奇妙之處了。make 只是構(gòu)建 需要構(gòu)建的部分,而不是簡單地重新構(gòu)建所有的內(nèi)容。由于所有的目標(biāo)文件都存在,make 確定沒有任何事情需要做。 我們可以證明這一點,通過刪除一個目標(biāo)文件,然后再次運行 make 程序,看看它做些什么。讓我們?nèi)サ粢粋€中間目標(biāo)文件:

[me@linuxbox diction-1.11]$ rm getopt.o
[me@linuxbox diction-1.11]$ make

我們看到 make 重新構(gòu)建了 getopt.o 文件,并重新鏈接了 diction 和 style 程序,因為它們依賴于丟失的模塊。 這種行為也指出了 make 程序的另一個重要特征:它保持目標(biāo)文件是最新的。make 堅持目標(biāo)文件要新于它們的依賴文件。 這個非常有意義,做為一名程序員,經(jīng)常會更新一點兒源碼,然后使用 make 來構(gòu)建一個新版本的成品。make 確保 基于更新的代碼構(gòu)建了需要構(gòu)建的內(nèi)容。如果我們使用 touch 程序,來“更新”其中一個源碼文件,我們看到發(fā)生了這樣的事情:

[me@linuxboxdiction-1.11]$ ls -l diction getopt.c
-rwxr-xr-x 1 me me 37164 2009-03-05 06:14 diction
-rw-r--r-- 1 me me 33125 2007-03-30 17:45 getopt.c
[me@linuxboxdiction-1.11]$ touch getopt.c
[me@linuxboxdiction-1.11]$ ls -l diction getopt.c
-rwxr-xr-x 1 me me 37164 2009-03-05 06:14 diction
-rw-r--r-- 1 me me 33125 2009-03-05 06:23 getopt.c
[me@linuxbox diction-1.11]$ make

運行 make 之后,我們看到目標(biāo)文件已經(jīng)更新于它的依賴文件:

[me@linuxbox diction-1.11]$ ls -l diction getopt.c
-rwxr-xr-x 1 me me 37164 2009-03-05 06:24 diction
-rw-r--r-- 1 me me 33125 2009-03-05 06:23 getopt.c

make 程序這種智能地只構(gòu)建所需要構(gòu)建的內(nèi)容的特性,對程序來說,是巨大的福利。雖然在我們的小項目中,節(jié)省的時間可能 不是非常明顯,在龐大的工程中,它具有非常重大的意義。記住,Linux 內(nèi)核(一個經(jīng)歷著不斷修改和改進的程序)包含了幾百萬行代碼。

安裝程序

打包良好的源碼經(jīng)常包括一個特別的 make 目標(biāo)文件,叫做 install。這個目標(biāo)文件將在系統(tǒng)目錄中安裝最終的產(chǎn)品,以供使用。 通常,這個目錄是 /usr/local/bin,為在本地所構(gòu)建軟件的傳統(tǒng)安裝位置。然而,通常普通用戶不能寫入該目錄,所以我們必須變成超級用戶, 來執(zhí)行安裝操作:

[me@linuxbox diction-1.11]$ sudo make install
After we perform the installation, we can check that the program is ready to go:
[me@linuxbox diction-1.11]$ which diction
/usr/local/bin/diction
[me@linuxbox diction-1.11]$ man diction
And there we have it!

總結(jié)

在這一章中,我們已經(jīng)知道了三個簡單命令:

./configure
make
make install

可以用來構(gòu)建許多源碼包。我們也知道了在程序維護過程中,make 程序起到了舉足輕重的作用。make 程序可以用到 任何需要維護一個目標(biāo)/依賴關(guān)系的任務(wù)中,不僅僅為了編譯源代碼。

拓展閱讀