第七週 linux如何裝載和啓動一個可執行文件

潘恆 原創做品轉載請註明出處 《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、實驗步驟及截圖

  1. 更新menu內核
    rm menu -rf
  2. 查看test.c文件(shift+G直接到文件尾):能夠看到新增長了exec系統調用,其源代碼與以前的fork相似
  3. 啓動內核並驗證execv函數
  4. 凍結內核,啓動GDB調試
  5. 進行調試
    • 先停在sys_execve處,再設置其它斷點;按c一路運行下去直到斷點sys_execve
    • 按s跳入函數內單步執行
  6. 退出調試狀態,輸入redelf -h hello能夠查看hello的EIF頭部

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裝載的大體過程,中間實現了動態連接。

相關文章
相關標籤/搜索