陳民禾——原創作品轉載請注明出處—— 《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000
一.復習上周內容
上周主要學習了Linux中的系統調用的過程,如圖所示就是系統調用的大致過程:
一.關於進程調度的一些基本概念
fork():進程是處於執行期的程序以及相關資源的總稱,進程在創建它的時候開始存活,在Linux系統中。這通常是調用fork()系統的結果,該系統調用通過復制一個現有進程來創建一個全新的進程,調用fork()的進程成為父進程,新產生的進程稱之為子進程,在調用結束時,在返回點這個相同位置上,父進程恢復執行,子進程開始執行,fork()系統從內核返回兩次:一次返回到父進程,另一次回到新的子進程。其中fork()實際上是由clone()系統調用實現的。
exec():創建新的進城之后會立即執行新的進程,接着調用exec()這組函數就可以創建新的地址空間,並把新的地址空間載入其中。
進程描述符:內核把進程的列表存放在叫做任務隊列的雙向循環列表中,鏈表中的類型都是task_struct、成為進程描述符的結構,進程描述符包含一個具體進程的所有信息,能完整的描述一個正在執行的程序:它打開的文件,進程的地址空間,掛起的信號,進程的狀態,還有其它的更多信息。
thread_info:每個進程的task_struct存放在內核棧的尾端,
進程狀態轉換:
- TASK_RUNNING具體是就緒還是執行,要看系統當前的資源分配情況;
- TASK_ZOMBIE也叫僵屍進程
三.進程創建
3.1 寫時拷貝
只有在需要寫入的時候,數據才會被復制,從而使各個進程擁有各自的拷貝。也就是說,資
源的復制只有在需要寫入的時候才進行,在此之前,只是以只讀方式共享。這種技術使地址空間上的頁的拷貝被推遲到實際發生寫入的時候才進行。在頁根本不會被寫人的情況下(舉例來說,fork()后立即調用exec(})它們就無須復制了。fork()的實際開銷就是復制父進程的頁表以反給予進程創建唯一的進程描述符。在一般情況下,進程創建后都會馬上運行一個可執行的文件,這種優化可以避免拷貝大量根本就不會被使用的數據〈地址空間里常常包含數十她的數據〉。由於Unix 強調進程快速執行的能力,所以這個優化是很重要的。
3. 2 fork()
Linux 通過clone()系統調用實現fork() 。這個調用通過一系列的參數標志來指明父、子進程需要共享的資源〈關於這些標志更多的信息請參考本章后面3.4 節〉。fork()、vfork()和一clone()庫函數都根據各自需要的參數楊L志去調用clone(),然后由clone()去調用do_fork().do_fork 完成了創建中的大部分工作,它的定義在kemeVfork.c 文件中。該函數調用copy_process()函數,然后讓進程開始運行。copy_process()函數完成的工作很有意思:
l )調用dup_task_ struct()為新進程創建一個內核枝、也read_info 結構和task_struct,這些值與當前進程的值相同。此時, 子進程和父進程的描述符是完全相同的。
2 )檢查並確保新創建這個子進程后,當前用戶所擁有的進程數目沒有超出繪色分配的資源
的限制.
3 )子進程着手使自己與父進程區別開來。進程描述符內的許多成員都要被清0 或設為初始值.那些不是繼承而來的進程描述符成員,主要是統計信息。task_struct 中的大多數數據都依然未被修改.
4 ) 子進程的狀態被設置為TASK_UNJNTERRUPTIBLE,以保證它不會投入運行。
5 ) copy _process()調用copy_flags()以更新task_struct 的組ags 成員.表明進程是否擁有超級用戶權限的PF_SUPE盯RIV 標志被清0。表明進程還沒有調用exec()函數的PF_FOR.KNOEXEC標志被設置。
6 )調用alloc _pid()為新進程分配一個有效的PID。
7 )根據傳遞給clone()的參數標志, copy_process()拷貝或共享打開的文件、文件系統信息、信號處理函數、進程地址空間和命名空間等。在一般情況下,這些資源會被給定進程的所有線程共享:否則,這些資源對每個進程是不同的,因此被拷貝到這里。的最后, copy_process()傲掃尾工作並返回一個指向子進程的指針。再回到do_fork()函數,如果copy_process()函數成功返回,新創建的子進程被喚醒並讓其投入運行。內核有意選擇子進程首先執行。.因為一般子進程都會馬上調用exec()函數,這樣可以避免寫時拷貝的額外開銷,如果父進程首先執行的話,有可能會開始向地址空間寫入。
四.實驗過程
1.更新menu內核,然后刪除test_fork.c以及test.c(以減少對之后實驗的影響)
2.編譯內核,可以看到fork命令
3.啟動gdb調試,並對主要的函數設置斷點
4.在MenuOS中執行fork,就會發現fork函數停在了父進程中
5.繼續執行之后,停在了do_fork的位置。然后n單步執行,依次進入copy_process、dup_task_struct。按s進入該函數,可以看到dst = src(也就是復制父進程的struct)
6.在copy_thread中,可以看到把task_pg_regs(p)也就是內核堆棧特定的地址找到並初始化
7.到了159、160行的代碼就是把壓入的代碼再放到子進程中:
*children = *current_pt_regs(); childregs->ax = 0;
8.164行,是確定返回地址
p->thread.ip = (unsigned long) ret_from_fork;
實驗感想:
只是我第一次這么早就完成博客,在這次實驗的過程中我了解了進程間調度的基本方法,還自己實踐了gdb對內核代碼的調試,很有意義。可執行程序代碼( Unix 稱其為代碼段, text section)。通常進程還要包含其他資源,像打開的文件,掛起的信號,內核內部數據,處理器狀態, 一個或多個具有內存映射的內存地址空間及一個或多個執行線程( thread of execution ),當然還包括用來存放全局變量的數據段等。實際上,進程就是正在執行的程序代碼的實時結果。