Android 筆記-LinuxKernel SMP (Symmetric Multi-Processors) 開機流程解析 Part(4)Linux 多核心啟動流程-kthreadd與相關的核心模組
by loda
hlchou@mail2000.com.tw
Loda's Blog Android/Linux Source Code Tags
App BizOrz
BizOrz.COM
BizOrz Blog
kthread第一次出現在LinuxKernel中是在Kernel版本2.6.4時,一開始的實作尚未有本文提到的kthreaddTask的具體架構,隨著版本的演進,除了這部份的設計完整外,需要產生KernelThread的實作也都已經改用kthread機制.
本文會先針對kthreaddTask行為加以說明,並會以在啟動後,屬於KernelMode產生的KernelThread的個別行為與功能做一個介紹,由於這部份涉及的範圍不少,筆者會以自己的角度選擇認為值得加以說明的項目,相信應該足以涵蓋大多數人對於LinuxKernel Mode Tasks所需的範圍.如果有所不足之處,也請透過LinuxKernel Hacking加以探索.
簡要來說,位於KernelMode的Tasks產生,除了直接透過kernel_thread函式外,還可以有兩個來源,一個是透過kthread_create(實作上,也是透過函式Kernel_Thread)產生的KernelThread,另一個則是透過WorkQueue機制產生的KernelThread,前者可以依據設計者自己對系統架構的掌握,去設計多工機制(例如,使用稍後提到的LinkedList Queue).而後者,則是由LinuxKernel提供延遲處理工作的機制,讓每個核心的Tasks可以透過WorkQueue機制把要延遲處理/指定處理器/指定延遲時間的工作,交派給WorkQueue.
在實際的應用上,WorkQueue還可以用以實現中斷的BottomHalf機制,讓中斷觸發時對Timing要求高的部分(TopHalf)可以在InterruptHandler中盡快執行完畢,透過WorkQueue把需要較長時間執行的部分(BottomHalf)交由WorkQueue延遲執行實現.(等同於RTOS下的LISR/HISR).
LinuxKernel的基礎Tasks.
在LinuxKernel中有三個最基礎的Tasks,分別為PID=0的IdleTask,PID=1負責初始化所有使用者環境與行程的init Task,與PID=2負責產生KernelMode行程的kthreaddTask.
其中,IdleTask主要用來在系統沒有其他工作執行時,可以執行省電機制(PMIdle)或透過IdleMigration把多核心其它處理器上的工作進行重分配(LoadBalance),讓處於Idle的處理器可以分擔Task工作,充分利用系統運算資源.
而initTask是所有UserMode Tasks的父行程,包含啟動時的ShellScript執行,或是載入必要的應用程式,都會基於initTask的執行來實現.
再來就是本文主要談的kthreaddTask,這是在LinuxKernel 2.6所引入的機制,在這之前要產生KernelThread需直接使用函式kernel_thread,而所產生的KernelThread父行程會是當下產生KernelThread的行程(或該父行程結束後,改為initTask(PID=1)).在kthreadd的機制下,UserMode與KernelMode Task的產生方式做了調整,並讓kthreaddTask成為使用kthread_create產生的KernelThread統一的父行程.也因此,在這機制實現下的LinuxKernel,屬於UserMode行程最上層的父行程為initTask (PID=1),而屬於KernelMode行程最上層的父行程為kthreaddTask (PID=2),而這兩個行程共同的父行程就是idle Task (PID=0).
kthreadd
kthreaddKernel Thread主要實作在檔案kernel/kthread.c中,入口函式為kthreadd(宣告為intkthreadd(void *unused) ),主要負責的工作是檢視目前LinkedList Queue "kthread_create_list"是否有要產生KernelThread的需求,若有,就呼叫函式create_kthread進行後續的產生工作.而要透過kthreadd產生KernelThread需求,就可以透過呼叫kthread_create(與其他衍生的kthread函式,ex:kthread_create_on_node....etc.)把需求加入到LinkedList Queue "kthread_create_list"中,並WakeUpkthreadd Task,就可以使用目前kthreadd新設計的機制.
有關函式kthreadd內部的運作流程,概述如下
1,執行set_task_comm(tsk,"kthreadd"),設定Task的執行檔名稱,會把"kthreadd"複製給task_struct中的comm (structtask_struct宣告在include/linux/sched.h).
2,執行ignore_signals(tsk),其中tsk= current,會設定讓Taskkthreadd忽略所有的Signals
3,設定kthreadd可以在所有處理器上執行.
4,進入kthreadd的for(;;)無窮迴圈,
4-1,透過函式list_empty確認kthread_create_list是否為空,若為空,就觸發Task排程
4-2,透過list_entry,取出要產生的KernelThread的”structkthread_create_info” Pointer
4-3,呼叫create_kthread產生KernelThread.
4-3-1,在create_kthread中會以"pid= kernel_thread(kthread, create, CLONE_FS | CLONE_FILES |SIGCHLD);"呼叫函式kernel_thread(inarch/arm/kernel/process.c),其中,入口函式為kthread,新產生的Task的第一個函式參數為create.
4-3-1-1,在函式kernel_thread中,會執行如下的程式碼,把最後新產生的KernelThread入口函式指給新Task的暫存器r5,要傳遞給該入口函式的變數只給暫存器r4,而該入口函式結束時要返回的函式kernel_thread_exit位址指給暫存器r7,並透過透過do_fork產生新的行程時,暫存器PC(Program Counter)指向函式kernel_thread_helper.也就是說每一個KernelThread的第一個函式統一都是kernel_thread_helper,而結束函式統一都為kernel_thread_exit.函式kernel_thread的參考程式碼如下所示
/*
*Create a kernel thread.
*/
pid_tkernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
structpt_regs regs;
memset(®s,0, sizeof(regs));
regs.ARM_r4= (unsigned long)arg;
regs.ARM_r5= (unsigned long)fn;
regs.ARM_r6= (unsigned long)kernel_thread_exit;
regs.ARM_r7= SVC_MODE | PSR_ENDSTATE | PSR_ISETSTATE;
regs.ARM_pc= (unsigned long)kernel_thread_helper;
regs.ARM_cpsr= regs.ARM_r7 | PSR_I_BIT;
returndo_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, ®s, 0, NULL, NULL);
}
新產生的Task會執行函式kernel_thread_helper,並把結束函式由暫存器r6指給暫存器LR(LinkerRegister),把入口函式的第一個參數指給暫存器r0,新Task的入口函式由暫存器r5指給暫存器PC,開始新Task的執行.有關函式kernel_thread_helper的參考程式碼如下所示
externvoid kernel_thread_helper(void);
asm( ".pushsection .text\n"
" .align\n"
" .type kernel_thread_helper, #function\n"
"kernel_thread_helper:\n"
#ifdefCONFIG_TRACE_IRQFLAGS
" bl trace_hardirqs_on\n"
#endif
" msr cpsr_c, r7\n"
" mov r0, r4\n"
" mov lr, r6\n"
" mov pc, r5\n"
" .size kernel_thread_helper, . - kernel_thread_helper\n"
" .popsection");
由於新Task的入口函式統一為"kthread",第一個函式參數統一為”structkthread_create_info*create”,檢視函式kthread的實作,可以看到在新行程由kernel_thread_helper呼叫進入kthread後,就會執行函式參數create中的create->threadfn函式指標,執行其他應用透過kthread_create產生KernelThread時的最終函式入口,參考代碼如下所示