start_kernel
到第一個任務: Linux Scheduler 閱讀筆記 (2)在前篇 Review with code: Linux Scheduler 閱讀筆記 (1) 我們提及了 sched_fork()
,又再往上追溯到了 kernel_clone()
,那又是被誰呼叫的呢?
第一個行程 (PID = 1) 是怎麼產生的?CPU Scheduler 又是怎麼被初始化的?我們嘗試回答以上問題,以便對 Linux CPU Sceheduler 有更深的了解。
以下程式碼為 Linux 核心 v6.8-rc7
版本。
我們大概都知道 bootstrap 的流程大概是 BIOS -> MBR -> boot loader -> kernel,前三者多是硬體主導,而載入 Kernel 後才是我們現在關心的;我們知道 boot loader 會載入 kernel,kernel 會將自己放到記憶體後開始一連串的初始化以及測試,也就是作業系統了,要回答開頭的問題,我們能夠由此下手。
以下我們此 call hierachy 開始理解,從 start_kernel()
到我們熟知的 kernel/sched/fair.c
中的 _fair()
函式:
sched_init()
start_kernel()
定義在 init/main.c
,被認為是 Linux Kernel 正式的進入點,它會執行一連串子模組的初始化,包含我們最感興趣的 sched_init()
,它也會啟動 PID = 1 的第一個行程執行 kernel_init
。
sched_init()
最顯眼的程式碼之一莫過於歷遍各個 CPU 初始化其 run queue:
user_mode_thread()
與 kernel_thread()
雖然上圖是以 kernel_thread()
啟動 kernel_init
,但在 v6.8-rc7 中,事實上是使用 pid = user_mode_thread(kernel_init, NULL, CLONE_FS);
,原因是 kernel_init
是時上不需要 kernel thread,因它最終仍會成為 user process;注意此處的見 user_mode_thread 並非常規的 user thread,可以注意到它並不具有用於分辨 user thread 和 kernel thread 的 mm
部分,詳細見
Patch: kthread: Unify kernel_thread() and user_mode_thread()
以及
Patch: kthread: Rename user_mode_thread() to kmuser_thread()
我們還可以於此比較一下 user_mode_thread()
和 kernel_thread()
的差異:
可以看到,兩者的差別只在於傳入參數是否帶有 name
以及 kthread = 1
而已,但如同前述所提 user_mode_thread
並非 user thread,而是 "special kernel threads"。
使用 sys_clone3()
:
關鍵在於傳入的 kargs
是否有帶有 user thread 的資訊,並交由後續的 kernel_clone()
以及 copy_process()
;注意到在 copy_process()
中的 copy_mm()
:
如果是 clone kernel thread,也就是沒有 ->mm
,那就創建一個同樣沒有 ->mm
的,反之亦然;或者這樣理解,若由 user thread 發起 cloning (sys_clone3
),就創造 user thread,反之,由 kernel_thread()
發起,則創造 kernel thread。
kernel_clone()
and copy_process()
上面兩個函式的實作是隱藏在 kernel_clone()
的,而其又會呼叫 copy_process()
,後者是一個長約五百行的函式,定義在 kernel/fork.c
,其重要性可見一班;而在其之後,必須要呼叫 wake_up_new_task()
才能夠算是完整地創造了一個新行程,其定義在 kernel/sched/core.c
。
__kthread_bind_mask
__kthread_bind_mask
將每個 kworker
按照預設的 CPU affinity 綁定在不同的 CPU 上,有幾個 CPU 就會有相對應的每個 kworker
,或是應該反過來說,kworker
的數量代表的就是 CPU 的數量;多核心處理由此開始。
透過 gdb 中斷 __kthread_bind_mask
可以看到,當 kworker/0:0
和 kworker/0:1
被創造時,其 mask->bits
為 1,而 kworker/1:0
和 kworker/1:1
為 2。
我們若使用 ps
指令能夠看到許多正在運行的行程,尤其在使用 KVM 時,能夠時刻觀察到從開始運行到完成開機有許多行程陸續被運行,其中大多都是 Kernel Threads,那他們的作用分別是什麼?又,行程一個最小完整的 CPU 需要哪些 Kernel Threads?
透過以上 qemu 指令,我們可以模擬一個雙核 x86 系統,並在開機後看到以下行程
PID 49 以前的看起來都非常像 Kernel Threads,我們可以將其分類並理解其用途
swapper/0
: idle thread
init
: inital user-space process
kthreadd
: the kernel thread daemon responsible for creating other kernel threads
ksoftirqd/n
: handles software interrupts (softirqs) on CPU n.
migration/n
: manages process migration between CPUs.
cpuhp/n
: manages CPU hotplug operations for CPU n.