1. 程式人生 > >從整理上理解進程創建、可執行文件的加載和進程執行進程切換,重點理解分析fork、execve和進程切換

從整理上理解進程創建、可執行文件的加載和進程執行進程切換,重點理解分析fork、execve和進程切換

files 修改 eve rdquo ces 堆棧分配 初始 data led

一.進程的創建

  Linux創建進程是通過子進程復制父進程所擁有的資源來實現的。現代Linux通過寫時復制、共享數據等方法優化這一過程,提高創建子進程的效率。

  在Linux中,進程創建實際上是通過do_fork函數處理的。do_fork函數的功能相對簡單:  

代碼在:kernel/fork.c
1.檢查是否或者哪個事件應該匯報給ptracer。
2.通過copy_process創建進程描述符和子進程執行所需要的其它數據結構。
3.執行wake_up_new_task函數,喚醒新進程。
4.結束並返回子進程的ID

copy_process則負責對進程創建的相關資源的申請:
代碼在:kernel/fork.c
1.調用security_task_create以及稍後調用的security_task_alloc執行附加的安全檢查。
2.執行dup_task_struct復制父進程的task_struct描述符
3.初始化新結構體的各個字段:did_exec,utime,stime,gtime,irq_events,hardirqs_enabled等等
4.進行調度相關的初始化:perf_event_init_task,audit_alloc.
5.復制父進程的信息到子進程:copy_semundo,copy_files,copy_fs,copy_mm等
6.初始化其它進程相關字段 7.將total_forks增加1

task_struct進程控制塊與進程地址空間的聯系:

  在task_struct結構體內的struct mm_struct成員執行內存區描述符的指針。在進程描述符中,還應該存儲進程空間的頁表信息,和將邏輯地址轉換成頁號和頁內偏移地址所需的相關信息。

  通過總結可以得到:進程的創建的系統調用clone fork vfork都是調用do_fork實現的,而do_fork在做了一些參數檢查之後。調用了copy_process函數,copy_process函數在進行安全性檢查之後,使用dup_task_struct復制父進程的結構體。對新進程描述符的一些標誌信息和時間信息進行初始化,之後將父進程的所有進程信息拷貝到子進程空間,包括IO、文件、內存信息等。然後,設置新進程的pid,將新進程加入進程調度隊列中。子進程的eax設置為0,父進程則返回新進程的pid,所以在fork調用中,子進程返回的是0,父進程返回的是新進程的pid。詳細代碼分析,請參加附錄。

二.可執行程序的加載

  在Linux中提供了一系列的函數,這些函數能用可執行文件所描述的新上下文代替進程的上下文。這樣的函數名以前綴exec開始。所有的exec函數都是調用了execve()系統調用。

  sys_execve接受參數:1.可執行文件的路徑 2.命令行參數字符串 3.環境變量字符串

  sys_execve是調用do_execve實現的。do_execve則是調用do_execve_common實現的,依次執行以下操作:

do_exceve的代碼在:fs/exec.h
1. 在堆上分配一個linux_binprm結構。
2. 調用open_exec讀取可執行文件。
3. 調用sched_exec(),確定最小負載的CPU以執行新程序,並把當前進程轉移過去。
4. 調用bprm_mm_init()函數,為新程序初始化內存管理。
5. 調用prepare_bprm()函數填充linux_binprm數據結構。
6. 拷貝命令行參數argv,環境變量envp,可執行文件名filename到新進程中
7. 調用search_binary_handler()函數對formats鏈表進行掃描,並嘗試每個load_binary函數,如果成功加載了文件的執行格式,對formats的掃描終止。
8. 釋放linux_binprm數據結構,返回從該文件可執行格式的load_binary中獲得的代碼。
復制代碼

對load_binary分析如下:

1.檢查存放在文件前128字節的一些魔數進行匹配以確認文件格式。
2.讀可執行文件的首部。
3.從可執行文件中確定動態鏈接程序的路徑名,並用它來確定共享庫的位置並把他們映射到內存。
4.獲得動態鏈接程序的目錄項對象。
5.檢查動態鏈接的執行許可權。
6.把動態鏈接程序的前128字節拷貝到緩沖區。
7.對動態鏈接程序類型執行一致性檢查。
8.調用arch_pick_mmap_layout(),以選擇進程線性區的布局。
9.調用setup_arg_pages()函數為進程的用戶態堆棧分配一個新的線性區描述符。
10.調用do_mmap()函數創建一個新線性區來對可執行文件正文段進行映射。
11.調用裝入動態鏈接程序的函數
12.把可執行格式的linux_binfmt對象的地址放在進程描述符的binfmt字段。
13.創建動態鏈接程序表,並把它們放在用戶態堆棧。
14.調用do_brk()函數創建一個新的匿名線性區來映射程序的bss段。
15.調用start_thread()宏修改保存在內核態堆棧。
16.返回0.

總結:Linux內核使用execve系統調用就開始進行程序的裝載。execve()系統調用相應的實現是sys_execve()。

  sys_execve()進行一些參數檢查復制之後,調用do_execve()。

  do_execve會首先查找被執行的文件,如果找打文件。則讀取文件的前128個字節以確定被檢查的文件的格式。然後,繼續調用search_binary_handle()去搜索合適的可執行文件裝載處理過程。

  對ELF可執行文件的裝載處理工程是load_elf_binary()。步驟是:檢查elf文件的有效性,尋找動態鏈接庫的“.interp”段,設置動態鏈接器的路徑,根據ELF的可執行文件的程序頭表的描述,對ELF文件進行映射,初始化ELF進程環境。

  最後,將系統調用的返回地址修改為ELF可執行文件的入口點。在設置了eip之後,sys_execve返回到用戶態時,程序就返回到新的程序開始執行,ELF可執行文件裝載完成。

  詳細的代碼分析,請參加附錄。

  ELF文件格式與進程地址空間的聯系:在壯載ELF文件時,會將文件頭部的.init、.text、.rodata段裝載到進程的代碼段中,將ELF中數據相關的部分,裝載到數據段中。

  在動態鏈接的過程中,ELF文件全局變量的數據區的部分會在進程空間中拷貝一份,代碼段則多個進程進行共享。

  exec執行之後,EIP指向的地方:EIP指向新程序的入口點,在sys_execve執行完成進入用戶態之後,將從新的程序入口點繼續執行。
  

從整理上理解進程創建、可執行文件的加載和進程執行進程切換,重點理解分析fork、execve和進程切換