linux核心初始化階段-fork內嵌問題

胖白白發表於2024-05-24

目錄
  • 1.在linux核心初始化程式中fork需要_syscall0(int,fork) 的背景
    • 1.1.背景
    • 1.2.重點來了-為啥需要_syscall0(int,fork)
  • 2.行內函數 + 宏定義的作用
  • 3.參考

1.在linux核心初始化程式中fork需要_syscall0(int,fork) 的背景

1.1.背景

  • 核心的main中線進行了所有硬體初始化工作,包括陷阱門、塊裝置、字元裝置和tty,包括人工設定第一個任務(task 0)。待所有初始化工作完成後就設定中斷允許標誌以開啟中斷,main()也切換到了任務0中執行。
  • 在整個核心完成初始化後,核心將執行權切換到了使用者模式(任務0),也即CPU 從0 特權級切換到了第3 特權級。此時main.c 的主程式就工作在任務0 中。然後系統第一次呼叫程序建立函式fork(),建立出一個用於執行init()的子程序。
    image
  • 此後程式把自己“手工”移動到任務0(程序0)中執行,並使用fork()呼叫首次建立出程序1(init 程序)。在init 程序中程式將繼續進行應用環境的初始化並執行shell 登入程式。而原程序0 則會在系統空閒時被排程執行,此時任務0 僅執行pause()系統呼叫,並又會呼叫排程函式。
  • 在 init 程序中,如果終端環境建立成功,則會再生成一個子程序(程序2),用於執行shell 程式/bin/sh。若該子程序退出,則父程序進入一個死迴圈內,繼續生成子程序,並在此子程序中再次執行shell 程式/bin/sh,而父程序則繼續等待。

1.2.重點來了-為啥需要_syscall0(int,fork)

  • 由於建立新程序的過程是透過完全複製父程序程式碼段和資料段的方式實現的,因此在首次使用fork()建立新程序init 時,為了確保新程序使用者態堆疊沒有程序0 的多餘資訊,要求程序0 在建立首個新程序之前不要使用使用者態堆疊,也即要求任務0 不要呼叫函式。因此在main.c 主程式移動到任務0 執行後,任務0 中的程式碼fork()不能以函式形式進行呼叫。程式中實現的方法是採用gcc 函式內嵌的形式來執行這個系統呼叫
static inline _syscall0(int,fork)
// 這是unistd.h 中的內嵌宏程式碼。以嵌入彙編的形式呼叫Linux 的系統呼叫中斷0x80。該中斷是所有
// 系統呼叫的入口。該條語句實際上是int fork()建立程序系統呼叫。
// syscall0 名稱中最後的0 表示無引數,1 表示1 個引數。參見include/unistd.h,133 行

image

// 本程式將會在移動到使用者模式(切換到任務0)後才執行fork(),因此避免了在核心空間寫時複製問題。
// 在執行了moveto_user_mode()之後,本程式就以任務0 的身份在執行了。而任務0 是所有將建立的子
// 程序的父程序。當建立第一個子程序時,任務0 的堆疊也會被複制。因此希望在main.c 執行在任務0
// 的環境下時不要有對堆疊的任何操作,以免弄亂堆疊,從而也不會弄亂所有子程序的堆疊

2.行內函數 + 宏定義的作用

  • 在編譯連結前會對原始檔進行預處理,它會將原始檔中的標頭檔案以及宏(在這裡就是把用#define定義的_syscall宏展開)都展開。
  • gcc會把上述“函式”體中的語句直接插入到呼叫fork()語句的程式碼處,因此執行fork()不會引起函式呼叫

3.參考

《Linux核心完全註釋》
寫時複製(COW)詳解
[fork()與_syscall0(int,fork) 關係](fork()與_syscall0(int,fork) 關係)
main.c中關於pause和fork的內嵌問題
Linux核心堆疊使用方法 程序0和程序1

相關文章