1. 程式人生 > >2018-2019-1 20189213《Linux核心原理與分析》第七週作業

2018-2019-1 20189213《Linux核心原理與分析》第七週作業

程序的描述和程序的建立

1.課本基礎知識彙總

作業系統核心實現作業系統的三大管理功能:即程序管理、記憶體管理、檔案系統。
對程序的描述:
在作業系統原理中,通過程序控制塊PCB描述程序;
在Linux核心中,通過一個數據結構struct task_struct來描述程序。

對程序狀態的描述:
在作業系統原理中,程序有就緒態、執行態和阻塞態;
在Linux核心中,就緒態和執行態都是相同的TASK_RUNNING狀態,另外再加上一個阻塞態;當程序是TASK_RUNNING狀態時,它是可執行的,也就是就緒態,是否在執行取決於它有沒有獲得CPU的控制權,也就是說這個程序有沒有在CPU中實際執行。如果在CPU中實際執行了,程序狀態就是執行態;如果被核心排程出去了,在等待佇列裡就是就緒態。
對於一個正在執行的程序,呼叫使用者態庫函式exit()會陷入核心執行該核心函式do_exit(),程序會進入TASK_ZOMBIE狀態,即程序的終止狀態。TASK_ZOMBIE狀態的程序一般叫做殭屍程序,Linux核心會在適當的時候把殭屍程序給處理掉,之後釋放程序描述符。一個正在執行的程序在等待特定事件或資源時會進入阻塞態,阻塞態也分為兩種:TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE。前者可以被訊號和wake_up()喚醒,而後者只能被wake_up()喚醒。

程序的建立

Linux中建立程序一共有三個函式:

  1. fork,建立子程序;
  2. vfork,與fork類似,但是父子程序共享地址空間,而且子程序先於父程序執行;
  3. clone,主要用於建立執行緒。

程序建立的相關主要程式碼:

通過上面的程式碼可以看出,fork、vfork和clone這3個系統呼叫和kernel_thread核心函式都可以建立一個新程序,而且都是通過do_fork函式來建立程序的,只不過傳遞的引數不同。

do_fork函式的引數:
clone_flags:子程序建立相關標誌,通過此標誌可以對父程序的資源進行有選擇的複製。
stack_start:子程序使用者態堆疊的地址。
regs:指向pt_regs結構體的指標。當發生系統呼叫時,int指令和SAVE_ALL儲存現場等會將CPU暫存器中的值按順序壓入核心棧。為了便於訪問操作,這部分資料被定義為pt_regs結構體。
stack_size:使用者態棧的大小,通常不必要,設定為0。
parent_tidptr和child_tidptr:父程序、子程序使用者態下的pid地址。

程序建立中的幾個關鍵函式:
do_fork():建立程序。
copy_process():建立程序內容(呼叫dup_task_struct、資訊檢查、初始化、更改程序狀態、複製其他程序資源、呼叫copy_thread初始化子程序核心棧、設定子程序pid等)。
dup_task_struct():複製當前程序(父程序)描述符task_struct,分配子程序核心棧。
copy_thread():核心棧關鍵資訊初始化。

實驗:分析Linux核心建立一個新程序的過程

本實驗主要用來跟蹤分析程序建立的完整過程,首先我們在MenuOS裡增加一個命令fork:

然後MenuOS正常執行,發現裡面也多了fork命令,我們執行fork命令:


下面我們使用gdb來進行跟蹤除錯:
由於fork指令實際上執行的就是sys_clone,所以我們可以在sys_clone、do_fork、dup_task_struct、copy_process、copy_thread、ret_from_fork等處各設定斷點:

然後開始執行,發現只輸出了一個命令描述,後面並沒有執行,而是停在了sys_clone這裡:

再繼續執行,停在copy_process:

再繼續執行,停在dup_task_struct函式,進入dup_task_struct內部,將當前程序核心壓棧壓得那一部分暫存器複製到子程序中,以及賦值子程序的起點。

問題

為什麼在建立子程序的時候,直接拷貝父程序的堆疊資訊到子程序中?
答:為了返回時是一致的狀態。
子程序退出時是否發生程序切換呢?
答:在syscall_exit中會呼叫syscall_exit_work函式。然後呼叫resume_userspace的時候就有可能會發生程序切換。
使用fork系統呼叫建立程序時,是否是先列印父程序後列印子程序?
答:父子程序的執行順序和相應的排程演算法密切相關,執行時執行順序是不確定的。

總結

程序是通過程序控制塊PCB來進行描述的,而核心要描述程序的結構,我們也稱其為程序描述符,下圖是程序描述符的結構示意圖:

實際上,使用者空間的暫存器、使用者態堆疊等資訊在切換到核心態的上下文時儲存在核心棧中,父程序在核心態(dup_task_struct)複製出子程序,但子程序作為一個獨立的程序,之後被排程執行時必須有一個指令地址,程序切換時,ip地址及當前核心棧的位置esp都存在於thread_info中,由copy_thread設定其thread.ip指向ret_from_fork作為子程序執行的第一條語句,並完成了核心態到使用者態的切換。