源碼要運行,必須先轉成二進制的機器碼。這是編譯器的任務。php
好比,下面這段源碼(假定文件名叫作test.c)。mysql
#include <stdio.h> int main(void) { fputs("Hello, world!\n", stdout); return 0; }
要先用編譯器處理一下,才能運行。nginx
$ gcc test.c $ ./a.out Hello, world!
對於複雜的項目,編譯過程還必須分紅三步。sql
$ ./configure $ make $ make install
編譯過程流程圖:緩存
編譯器在開始工做以前,須要知道當前的系統環境,好比標準庫在哪裏、軟件的安裝位置在哪裏、須要安裝哪些組件等等。這是由於不一樣計算機的系統環境不同,經過指定編譯參數,編譯器就能夠靈活適應環境,編譯出各類環境都能運行的機器碼。這個肯定編譯參數的步驟,就叫作"配置"(configure)。bash
這些配置信息保存在一個配置文件之中,約定俗成是一個叫作configure的腳本文件。一般它是由autoconf工具生成的。編譯器經過運行這個腳本,獲知編譯參數。app
configure腳本已經儘可能考慮到不一樣系統的差別,而且對各類編譯參數給出了默認值。若是用戶的系統環境比較特別,或者有一些特定的需求,就須要手動向configure腳本提供編譯參數。ide
$ ./configure --prefix=/www --with-mysql
上面代碼是php源碼的一種編譯配置,用戶指定安裝後的文件保存在www目錄,而且編譯時加入mysql模塊的支持。函數
源碼確定會用到標準庫函數(standard library)和頭文件(header)。它們能夠存放在系統的任意目錄中,編譯器實際上沒辦法自動檢測它們的位置,只有經過配置文件才能知道。工具
編譯的第二步,就是從配置文件中知道標準庫和頭文件的位置。通常來講,配置文件會給出一個清單,列出幾個具體的目錄。等到編譯時,編譯器就按順序到這幾個目錄中,尋找目標。
對於大型項目來講,源碼文件之間每每存在依賴關係,編譯器須要肯定編譯的前後順序。假定A文件依賴於B文件,編譯器應該保證作到下面兩點。
(1)只有在B文件編譯完成後,纔開始編譯A文件。 (2)當B文件發生變化時,A文件會被從新編譯。
編譯順序保存在一個叫作makefile的文件中,裏面列出哪一個文件先編譯,哪一個文件後編譯。而makefile文件由configure腳本運行生成,這就是爲何編譯時configure必須首先運行的緣由。
在肯定依賴關係的同時,編譯器也肯定了,編譯時會用到哪些頭文件。
不一樣的源碼文件,可能引用同一個頭文件(好比stdio.h)。編譯的時候,頭文件也必須一塊兒編譯。爲了節省時間,編譯器會在編譯源碼以前,先編譯頭文件。這保證了頭文件只需編譯一次,沒必要每次用到的時候,都從新編譯了。
不過,並非頭文件的全部內容,都會被預編譯。用來聲明宏的#define命令,就不會被預編譯。
預編譯完成後,編譯器就開始替換掉源碼中bash的頭文件和宏。以本文開頭的那段源碼爲例,它包含頭文件stdio.h,替換後的樣子以下。
extern int fputs(const char *, FILE *); extern FILE *stdout; int main(void) { fputs("Hello, world!\n", stdout); return 0; }
爲了便於閱讀,上面代碼只截取了頭文件中與源碼相關的那部分,即fputs和FILE的聲明,省略了stdio.h的其餘部分(由於它們很是長)。另外,上面代碼的頭文件沒有通過預編譯,而實際上,插入源碼的是預編譯後的結果。編譯器在這一步還會移除註釋。
這一步稱爲"預處理"(Preprocessing),由於完成以後,就要開始真正的處理了。
預處理以後,編譯器就開始生成機器碼。對於某些編譯器來講,還存在一箇中間步驟,會先把源碼轉爲彙編碼(assembly),而後再把彙編碼轉爲機器碼。
下面是本文開頭的那段源碼轉成的彙編碼。
.file "test.c" .section .rodata .LC0: .string "Hello, world!\n" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movq stdout(%rip), %rax movq %rax, %rcx movl $14, %edx movl $1, %esi movl $.LC0, %edi call fwrite movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Debian 4.9.1-19) 4.9.1" .section .note.GNU-stack,"",@progbits
這種轉碼後的文件稱爲對象文件(object file)。
注:make (gcc), 其調用 gcc 執行編譯的過程依賴於配置文件makefile
對象文件還不能運行,必須進一步轉成��執行文件。若是你仔細看上一步的轉碼結果,會發現其中引用了stdout函數和fwrite函數。也就是說,程序要正常運行,除了上面的代碼之外,還必須有stdout和fwrite這兩個函數的代碼,它們是由C語言的標準庫提供的。
編譯器的下一步工做,就是把外部函數的代碼(一般是後綴名爲.lib和.a的文件),添加到可執行文件中。這就叫作鏈接(linking)。這種經過拷貝,將外部函數庫添加到可執行文件的方式,叫作靜態鏈接(static linking),後文會提到還有動態鏈接(dynamic linking)。
make命令的做用,就是從第四步頭文件預編譯開始,一直到作完這一步。
上一步的鏈接是在內存中進行的,即編譯器在內存中生成了可執行文件。下一步,必須將可執行文件保存到用戶事先指定的安裝目錄。
表面上,這一步很簡單,就是將可執行文件(連帶相關的數據文件)拷貝過去就好了。可是實際上,這一步還必須完成建立目錄、保存文件、設置權限等步驟。這整個的保存過程就稱爲"安裝"(Installation)。
可執行文件安裝後,必須以某種方式通知操做系統,讓其知道可使用這個程序了。好比,咱們安裝了一個文本閱讀程序,每每但願雙擊txt文件,該程序就會自動運行。
這就要求在操做系統中,登記這個程序的元數據:文件名、文件描述、關聯後綴名等等。Linux系統中,這些信息一般保存在/usr/share/applications目錄下的.desktop文件中。另外,在Windows操做系統中,還須要在Start啓動菜單中,創建一個快捷方式。
這些事情就叫作"操做系統鏈接"。make install命令,就用來完成"安裝"和"操做系統鏈接"這兩步。
寫到這裏,源碼編譯的整個過程就基本完成了。可是隻有不多一部分用戶,願意耐着性子,從頭至尾作一遍這個過程。事實上,若是你只有源碼能夠交給用戶,他們會認定你是一個不友好的傢伙。大部分用戶要的是一個二進制的可執行程序,馬上就能運行。這就要求開發者,將上一步生成的可執行文件,作成能夠分發的安裝包。
因此,編譯器還必須有生成安裝包的功能。一般是將可執行文件(連帶相關的數據文件),以某種目錄結構,保存成壓縮文件包,交給用戶。
正常狀況下,到這一步,程序已經能夠運行了。至於運行期間(runtime)發生的事情,與編譯器一律無關。可是,開發者能夠在編譯階段選擇可執行文件鏈接外部函數庫的方式,究竟是靜態鏈接(編譯時鏈接),仍是動態鏈接(運行時鏈接)。因此,最後還要提一下,什麼叫作動態鏈接。
前面已經說過,靜態鏈接就是把外部函數庫,拷貝到可執行文件中。這樣作的好處是,適用範圍比較廣,不用擔憂用戶機器缺乏某個庫文件;缺點是安裝包會比較大,並且多個應用程序之間,沒法共享庫文件。動態鏈接的作法正好相反,外部函數庫不進入安裝包,只在運行時動態引用。好處是安裝包會比較小,多個應用程序能夠共享庫文件;缺點是用戶必須事先安裝好庫文件,並且版本和安裝位置都必須符合要求,不然就不能正常運行。
現實中,大部分軟件採用動態鏈接,共享庫文件。這種動態共享的庫文件,Linux平臺是後綴名爲.so的文件,Windows平臺是.dll文件,Mac平臺是.dylib文件。
1.編譯安裝源程序的前提:
1).提供開發環境:開發工具和開發庫
2).編譯安裝須要的包組:
Development Tools、Server Platform Development、Desktop Platform Development、Debug Tools
2.configure腳本經常使用的選項:
--help獲取./configure腳本幫助 --prefix=: 指定安裝路徑;多數程序都有默認安裝路徑; --sysconfidr=: 指定配置文件安裝路徑; --with-PACKAGE[=ARG]:在自由軟件社區裏,有使用已有軟件包和庫的優秀傳統.當用'configure'來配置一個源碼樹時, 能夠提供其餘已經安裝的軟件包的信息 --without-PACKAGE:有時候你可能不想讓你的軟件包與系統已有的軟件包交互。例如,你可能不想讓你的新編譯器使用 GNU ld --enable-FEATURE:一些軟件包可能提供了一些默認被禁止的特性,可使用'--enable-FEATURE'來起用它 --disable-EEATURE:關閉指定的默認特性
3.編譯安裝源程序方法:
1)、展開源代碼,找INSTALL、README;不存在此類文件時,找項目官方文檔;
2)、根據安裝說明執行安裝操做;
3.程序安裝於專用目錄時,安裝後的配置:
1)、導出二進制程序所在路徑至PATH環境中
# export PATH=/usr/local/nginx/sbin:$PATH 實現永久有效的辦法: /etc/profile.d/*.sh
2)、導出庫文件給OS
OS查找庫文件方法:根據/etc/ld.so.conf
配置文件指定的路徑搜索,或搜索/lib, /lib64, /usr/lib, /usr/lib64
,把查找到的全部的庫文件路徑和其名稱映射關係保存爲一個緩存文件/etc/ld.so.cache
;
/etc/ld.so.conf
配置文件有其它組成部分:/etc/ld.so.conf.d/*.conf
假設nginx安裝於/usr/local/nginx
,此目錄中有其庫文件子目錄lib,導出此目錄中庫文件:
(1)新建文件/etc/ld.so.conf.d/nginx.conf
,在文件添加以下行:
/usr/local/nginx/lib
(2) 運行命令:ldconfig
ldconfig的主要用途:
默認搜尋/lilb和/usr/lib,以及配置文件/etc/ld.so.conf內所列的目錄下的庫文件。
搜索出可共享的動態連接庫,庫文件的格式爲:lib***.so.**,進而建立出動態裝入程序(ld.so)所需的鏈接和緩存文件。
緩存文件默認爲/etc/ld.so.cache,該文件保存已排好序的動態連接庫名字列表。
ldconfig一般在系統啓動時運行,而當用戶安裝了一個新的動態連接庫時,就須要手工運行這個命令。
經常使用選項:
-v: 用此選項時,ldconfig將顯示正在掃描的目錄及搜索到的動態連接庫,還有它所建立的鏈接的名字. -p: 顯示當前OS已經加載到的全部庫文件名稱及其文件所在路徑的映射關係;
ldconfig須要注意的地方:
(a)、往/lib和/usr/lib裏面加東西,是不用修改/etc/ld.so.conf文件的,可是添加完後須要調用下ldconfig,否則添加的library會找不到。
(b)、若是添加的library不在/lib和/usr/lib裏面的話,就必定要修改/etc/ld.so.conf文件,往該文件追加library所在的路徑,而後也須要從新調用下ldconfig命令。好比在安裝mysql的時候,其庫文件/usr/local/mysql/lib,就須要追加到/etc/ld.so.conf文件中。命令以下:
# echo "/usr/local/mysql/lib" >> /etc/ld.so.conf # ldconfig -v | grep mysql
(c)、若是添加的library不在/lib或/usr/lib下,可是卻沒有權限操做寫/etc/ld.so.conf文件的話,這時就須要往export裏寫一個全局變量LD_LIBRARY_PATH,就能夠了。
(3)、幫助文件導出
man命令搜索特定路徑查找手冊頁文件,這些路徑是定義在/etc/man.config
中的MANPATH
參數所指定的路徑下的;
新增辦法:編輯/etc/man.config
文件,新增一個MANPATH
參數,其值爲新安裝程序的man手冊所在的目錄;
/usr/local/nginx/share/man/{man1,man8} man -M /path/to/man KEYWORD
(4)、頭文件導出
有些程序安裝後會生成對本身擁有庫文件調用接口相關頭文件系統查找頭文件的路徑爲/usr/include
導出獨立安裝應用程序的頭文件方法:建立連接至/usr/include
下便可;
例如:
/usr/local/nginx/include # ln -sv /usr/local/nginx/include/* /usr/include/ # ln -sv /usr/local/nginx/include /usr/include/nginx
perl源程序的編譯安裝方法:
(1) perl Makefile.in (2) make (3) make install