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

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

linux核心分析學習筆記 ——第六章 程序的描述和程序的建立

學習重點——子程序的建立以及執行流程


程序描述和程序的建立

作業系統的三大功能——程序管理、記憶體管理和檔案系統。
在linux核心中利用struct task_struct資料結構來描述程序。

其中包括了程序狀態state、stack堆疊、程序雙向連結串列struct list_head、控制檯tty、檔案系統fs的描述,程序開啟檔案的檔案描述files、記憶體管理的描述mm、程序間通訊的訊號signal等等。

程序狀態包括就緒臺、執行態和阻塞態這三種基本狀態。

  • TASK_RUNNING有兩個狀態,一個是就緒態但是沒有執行,另一個是執行態,這兩個狀態的轉換依賴於核心中的排程器

    另一種方式說TASK_RUNNING處執行態取決於它是否獲得了CPU的控制權;如果被核心排程出去,就在等待佇列中。
  • 對於正在執行的程序,呼叫使用者態庫函式exit()會陷入核心執行do_exit()進入TASK_ZOMBIE狀態。
  • 阻塞態也有兩種
    • TASK_UNINTERRUPTIBLE 可以被wake_up()喚醒
    • TASK_INTERRUPTIBLE 可以被wake_up()和訊號喚醒

程序的建立

0號程序的建立

在前面我們學到了,核心的第一個程序0號程序init_task的程序描述符結構體變數的初始化是通過硬編碼方式確定下來的,其他的所有程序都是通過do_fork的方式複製父程序來初始化的。

記憶體管理相關程式碼

每個程序都有資料段、程式碼段、堆疊段等,他們就是由這個資料結構統領起來的。

程序之間的關係

程序描述符通過雙向連結串列struct list_head_tasks雙向連結串列來管理所有程序。

  • real_partent parent用來記錄當前父程序
  • struct list_head_children雙向連結串列 記錄了當前的子程序
  • struct list_head_list_sibling雙向連結串列 記錄了當前的兄弟程序

儲存程序上下文中CPU相關資訊的資料結構

struct_thread_struct資料結構用來儲存程序上下文中一些資訊,最關鍵的是sp和ip,分別用來儲存ESP暫存器狀態和EIP暫存器狀態。

程序建立的基本過程

  • 將當前程序的描述符等相關程序資源複製一份
  • 子程序要對複製的程序描述符進行一些修改
  • 建立好的子程序放入執行佇列
  • 系統排程時,子程序將有機會被排程

do_fork()函式分析

不管是fork vfork還是clone 都是通過do_fork()函式建立程序。

觸發系統呼叫的過程

一般的系統呼叫,在使用者態 int $0x80 指令觸發中斷機制,跳轉到核心態執行system_call系統呼叫,此時會把使用者程序使用者態的堆疊SS:EIP和CS:EIP和EFLAGS都壓棧到當前核心堆疊中。最後iret恢復現場,回到使用者態。

do_fork()的引數

  • clone_flags 子程序建立的相關標誌,通過此標誌對父程序資源進行有選擇的複製
  • stack_start 子程序使用者態堆疊地址
  • stack_size 使用者棧大小
  • *parent_tidptr 指向父程序pid的指標
  • *child_tidptr 指向子程序pid的指標

其中函式copy_process()函式用來建立子程序的程序描述符和其他相關資料結構
函式weak_up_new_task()作業是將子程序新增到程序排程佇列。

copy_process()函式分析

copy_process中主要呼叫了dup_task_struct()和copy_thread()函式

其中dup_task_struct()複製當前的程序描述符、資訊檢查、初始化、設定程序為就緒態、採用寫時複製技術複製所有父程序的資源。
copy_thread()初始化了子程序的核心棧、設定子程序的pid
dup_task_struct()為子程序分配好了核心棧後,由copy_thread()初始化核心棧的關鍵資訊。


建立的如果為核心程序,則子程式程式執行的起點是ret_from_kernel_thread

如果為使用者程序,子程式開始執行的起點是ret_from_fork

以使用者態程序為例,子程式的起點是系統呼叫在核心態中的ret_from_fork,執行結束後,恢復現場返回使用者態,此時返回的使用者態堆疊是子程序的堆疊,執行子程序的相關函式

thread_info資料結構
  • 通過task指標指向程序描述符,同時task_struct的stack欄位指向本程序的thread_info結構首地址。
    -下圖所示是核心棧、 thread_info結構和程序描述符之間的關係。

  • thread_info記錄了部分程序資訊的結構體,包括了程序上下文資訊。

實驗跟蹤分析程序建立的過程

編譯好的MenuOS添加了fork功能,如下圖所示

下面進行除錯,為MenuOS新增斷點,分別在sys_clone do_fork dup_task_struct copy_process``copy_thread

在命令列中輸入fork發現停在sys_clone處,說明系統呼叫函式是sys_clone繼續執行會停在do_fork中,系統呼叫函式的示意圖大概是下圖所示


系統呼叫建立子程序函式

linux系統下來實現建立程序,它們封裝的對應的核心處理函式都是do_fork()

  • fork() 建立的子程序是父程序的完整副本,複製了父程序的資源,包括記憶體task_struct內容

    當程序A使用系統呼叫fork建立一個子程序B時,由於子程序B實際上是父程序A的一個拷貝,因此會擁有與父程序相同的物理頁面。

  • vfork()建立的子程序與父程序共享資料段,同時子程序會優先於父程序執行

    由vfok創建出來的子程序共享了父程序的所有記憶體,包括棧地址,直至子程序使用execve啟動新的應用程式為止

  • clone() linux建立執行緒一般使用pthread庫,clone是linux為我們提供的建立執行緒的系統呼叫

fork、vfork和clone的區別

fork()函式理解

fork()函式功能是建立一個子程序。程序的唯一標識是pid,利用系統呼叫getpid()可以獲得當前程序的程序號;利用getppid()可以獲得當前程序父程序的程序號。

而程序所產生的父程序則是根據fork()的返回值來確定的。成功建立子程序後,父程序獲得子程序的ID。而子程序獲得返回值0。

注意事項:

  • fork系統呼叫之後,父程序和子程序交替執行,並處於不同的空間中。
  • fork()函式一次呼叫返回2次返回。兩個程序處於不同的獨立空間中而至於是先子程序還是父程序先執行,這沒有確切的規定,是隨機的。
    ——— 即併發的特性

子程序是父程序的副本,它將獲得父程序資料空間、堆、棧等資源的副本。注意,子程序持有的是上述儲存空間的“副本”,這意味著父子程序間不共享這些儲存空間。

注意:複製核心堆疊的時候只是複製了核心堆疊的一部分,而不是全部複製。

  • fork()的子執行過程在fork()之後並不是從頭開始,父程序已經為子程序搭建好了執行環境,所以位元組有效程式碼處開始執行。

由於在複製時複製了父程序的堆疊段,所以兩個程序都停留在fork函式中,等待返回。因此fork函式會返回兩次,一次是在父程序中返回,另一次是在子程序中返回,這兩次的返回值是不一樣的。

返回值可以理解為連結串列,fork()的父程序在建立了子程序後,“指標”指向了子程序的pid,所以返回值為子程序pid,而子程序沒有指向,所以返回值為0