潘恆 原創做品轉載請註明出處 《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000linux
1、實驗內容shell
1.預處理、編譯和連接 實踐函數
ELF頭部在文件的開始,描述文件的整體格式,保存了路線圖,描述該文件的組織狀況,即生成該文件系統的字的大小和字節順序 段頭部表用來描述ELF可執行文件與連續的存儲段之間的映射關係。節頭表包含了描述文件節區的信息,每一個節區在表中都有一個項,給出節區的名稱、節區大小這類內心。用於連接的目標文件(可重定向文件)必須包含節區頭部表,而可執行文件能夠沒有。spa
2.可執行程序的執行環境操作系統
命令行參數和shell環境,通常咱們執行一個程序的Shell環境,咱們的實驗直接使用execve系統調用。命令行
$ ls -l /usr/bin列出/usr/bin下的目錄信息
Shell自己不限制命令行參數的個數,命令行參數的個數受限於命令自身3d
例如:指針
int main(int argc, char*argv[])
又如調試
int main(int argc, char*argv[], char *envp[])
Shell會調用execve將命令行參數和環境參數傳遞給可執行程序的main函數code
int execve(const char *filename,char * const argv[ ],char * const envp[ ]);
庫函數exec*都是execve的封裝例程
3.可執行程序的裝載
(1)do_execve()
(2)search_binary_handler()
(3) load_elf_binary()
(4) load_elf_interp()
2、實驗步驟及截圖
rm menu -rf
3、Linux系統加載可執行程序所需處理過程的理解
1. 新的可執行程序是從哪裏開始執行的?
當execve()系統調用終止且進程從新恢復它在用戶態執行時,執行上下文被大幅度改變,要執行的新程序已被映射到進程空間,從elf頭中的程序入口點開始執行新程序。
若是這個新程序是靜態連接的,那麼這個程序就能夠獨立運行,elf頭中的這個入口地址就是本程序的入口地址。
若是這個新程序是動態連接的,那麼此時還須要裝載共享庫,elf頭中的這個入口地址是動態連接器ld的入口地址。
2.爲何execve系統調用返回後新的可執行程序能順利執行?
新的可執行程序執行:
1. 須要的庫函數。
2. 屬於它的進程空間:代碼段,數據段,內核棧,用戶棧等。
3. 須要的運行參數。
4. 須要的系統資源。
若是知足以上4個條件,那麼新的可執行程序就會處於可運行態,只要被調度到,就能夠正常執行。
條件1:若是新進程是靜態連接的,那麼庫函數已經在可執行程序文件中,條件知足。若是是動態連接的,新進程的入口地址是動態連接器ld的起始地址,能夠完成對所需庫函數的加載,也能知足條件。
條件2:execve系統調用經過大幅度修改執行上下文,將用戶態堆棧清空,將老進程的進程空間替換爲新進程的進程空間,新進程從老進程那裏繼承了所須要的進程空間,條件知足。
條件3:咱們通常在shell中,輸入可執行程序所須要的參數,shell程序把這些參數用函數參數傳遞的方式傳給給execve系統調用,而後execve系統調用以系統調用參數傳遞的方式傳給sys_execve,最後sys_execve在初始化新程序的用戶態堆棧時,將這些參數放在main函數取參數的位置上。條件知足。
條件4:若是當前系統中沒有所須要的資源,那麼新進程會被掛起,直到資源有了,喚醒新進程,變爲可運行態,條件能夠知足。
綜上,新的可執行程序能夠順利執行。
3.對於靜態連接的可執行程序和動態連接的可執行程序execve系統調用返回時會有什麼不一樣?
execve系統調用會調用sys_execve,而後sys_execve調用do_execve,而後do_execve調用do_execve_common,而後do_execve_common調用exec_binprm。
對於ELF文件格式,fmt函數指針實際會執行load_elf_binary,load_elf_binary會調用start_thread,在start_thread中經過修改內核堆棧中EIP的值,使其指向elf_entry,跳轉到elf_entry執行。
對於靜態連接的可執行程序,elf_entry是新程序的執行起點。對於動態連接的可執行程序,須要先加載連接器ld,
elf_entry = load_elf_interp(…)
將CPU控制權交給ld來加載依賴庫,再由ld在完成加載工做後將CPU控制權還給新進程。
四.總結
在Linux中,fork是進程建立另外一個進程的惟一方法。只有第一個進程也就是被稱做 init 的進程須要 手工建立 。全部其餘進程都是用fork這個系統調用建立的。fork系統調用只是複製了父進程的數據和堆棧,並在這兩個進程之間共享文本區。fork系統調用採用比較聰明的方式— 寫時拷貝(copy-on-write) 技術,使得fork結束後並不馬上覆制父進程的內容,而是到了真正實用的時候才複製,這樣使效率大大提升。fork函數建立了一個子進程後,子進程會調用exec族函數執行另一個程序。 多進程、多用戶、虛擬存儲的操做系統出現之後,可執行文件的裝載過程變得很是複雜。引入了進程的虛擬地址空間;而後根據操做系統如何爲程序的代碼、數據、堆、棧在進程地址空間中分配,它們是如何分佈的;最後以頁映射的方式將程序映射進程虛擬地址空間。 動態連接是一種與靜態連接程序不一樣的概念,即一個單一的可執行文件模塊被拆分紅若干個模塊,在程序運行時進行連接的一種方式。而後根據實際例子do_exece()分析了ELF裝載的大體過程,中間實現了動態連接。