linux內核線程的建立與銷燬

linux將建立內核線程的工做交給了一個專門的內核線程kthreadd來完成,該線程會檢查全局鏈表kthread_create_list,若是爲NULL,就會調schedule()放棄cpu進入睡眠狀態,不然就取下該鏈表中的一項建立對應的線程。本文就從khtreadd內核線程的建立開始來展現一下內核線程的建立過程。
1 kthreadd
linux2.6.30,建立內核線程是經過kethradd內核守護線程來完成的,雖然機制上有所變化,可是最終仍是調用do_fork來完成線程的建立。Kthreadd守護線程是在linux內核啓動時就已經建立的內核線程。在start_kernel調用的結束位置,會調用rest_init接口,而kthreadd就是在這個接口中建立的,代碼以下所示:
asmlinkage void __init start_kernel(void)
{
。。。。。。。。。。。。。。。。。。。。。。
     rest_init();
}
static noinline void __init_refok rest_init(void)
     __releases(kernel_lock)
{            
     int pid;
     /*在啓動時建立內核線程,該線程主要用來建立內核線程*/
     pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
     kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
}
Kthreadd守護線程自己也是一個內核線程,這個內核線程本質上與其餘內核線程都同樣,只不過是這個內核線程所作的工做是用來建立內核線程的。函數的調用關係以下所示:
Start_kernel=》rest_init=》kernel_thread=》do_fork
沒有內核線程建立請求時,Kthreadd守護線程就處於睡眠狀態,一旦有內核線程建立請求,則喚醒kthreadd守護線程,完成內核線程的建立工做。
kthreadd守護線程主要經過傳入內核線程建立參數來建立特定的內核線程,所以,在講述內核線程的建立以前,有必要了解一下內核線程的建立參數。內核線程建立相關的數據結構主要是struct kthread_create_info,這個數據結構表示建立一個內核線程所須要的信息。結構體定義以下所示:
structkthread_create_info           
{
       /* Information passed to kthread() fromkthreadd. */
       int (*threadfn)(void *data);/*內核線程的回調函數*/
       void *data;/*回調函數的參數*/
       struct completion started;/*kthreadd等待新建立的線程啓動*/

       /* Result passed back to kthread_create()from kthreadd. */
       struct task_struct *result;/*建立成功以後返回的task_struct*/
       struct completion done;/*用戶等待線程建立完成*/

       struct list_head list;/*主要用於將建立參數結構放在一個全局鏈表中*/
};
用戶須要建立一個內核線程時,會填充該數據結構,並將該結構體掛在全局的kthread_create_list鏈表中,而後喚醒kthreadd守護線程來建立內核線程,而用戶線程則阻塞等待內核線程建立完成。詳細的用戶接口下文會介紹,這裏就不在贅述。
根據前面對內核線程建立結構體的描述,kthreadd守護線程須要完成的工做就比較簡單,主要就是遍歷kthread_create_list,若是鏈表中有須要建立的內核線程,則調用create_kthread完成內核線程的建立工做。反之,沒有內核線程須要建立,那麼kthreadd守護線程將睡眠,等待下次內核線程建立請求的喚醒。
static voidcreate_kthread(struct kthread_create_info *create)
{
     int pid;
     /* We want our own signal handler (wetake no signals by default). */
     pid =kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
     if (pid < 0)
            create->result = ERR_PTR(pid);
     else
            wait_for_completion(&create->started);/*等待新建立的線程啓動*/
     complete(&create->done);/*通知用戶請求建立的線程已經完成*/
}
create_kthread主要開始調用kernel_thread來建立內核線程,到這裏,內核線程的建立工做跟kthreadd內核守護線程建立過程就一致了,可謂是異曲同工。若是Kthreadd建立線程失敗,則直接通知用戶請求建立動做已經完成,至因而不是建立成功,須要用戶本身判斷。反之,內核線程建立成功,kthreadd守護線程會等待新建立的線程啓動,而後才通知用戶請求建立內核線程的動做已經完成。完成了一個內核線程的建立以後,從kthread_create_list鏈表中摘下下一個須要建立的內核線程,指定全部請求的內核線程建立成功,ktheadd線程睡眠。
2 kthread_create and kthread_run
既然linux2.6.30採用了kthreadd守護線程來建立內核線程,那麼在內核編程的時候,用戶就不該該直接調用kernel_thread來建立內核線程。(這裏只是猜想,具體細節有待研究)linux內核提供給用戶用來建立內核線程的接口有兩個,一個是kthread_create,另外一個是kthread_run。kthread_run接口其實也就是kthread_create的封裝。所不一樣的是,kthread_run在內核線程建立成功以後,就直接調用了wake_up_process,來喚醒該內核線程,使其可以馬上獲得調度,而經過kthread_create建立的內核線程默認是睡眠的,並不會獲得調度,而是須要顯示的喚醒該內核線程才能獲得調度。代碼以下所示:
structtask_struct * kthread_create(int (*threadfn)(void *data),
                                      void *data,
                                      const char namefmt[],
                                      ...)
{
       struct kthread_create_info create;
/*填充create結構體*/
       create.threadfn = threadfn;
       create.data = data;
       init_completion(&create.started);
       init_completion(&create.done);
/*將create結構體掛到全局的kthread_create_list鏈表中*/
       spin_lock(&kthread_create_lock);
       list_add_tail(&create.list,&kthread_create_list);
       spin_unlock(&kthread_create_lock);
/*喚醒kthradd守護線程來建立內核線程*/
       wake_up_process(kthreadd_task);
/*等待線程的建立結束*/
       wait_for_completion(&create.done);
/*kthreadd線程並不能保證100%建立成功,這裏須要在作驗證*/
       if (!IS_ERR(create.result)) {
                 struct sched_param param = {.sched_priority = 0 };
                 va_list args;
                  * root may have changed our (kthreadd's)priority or CPU mask.
                  * The kernel thread should not inherit theseproperties.
                  */
/*設置線程的優先級和cpu親和性*/
                 sched_setscheduler_nocheck(create.result,SCHED_NORMAL, ¶m);
                 set_user_nice(create.result,KTHREAD_NICE_LEVEL);
                 set_cpus_allowed_ptr(create.result,cpu_all_mask);
       }
       return create.result;
}
由前文可知,建立一個內核線程,首先要作的就是填充kthread_create_info結構體,將線程的處理函數以及相關參數傳遞給kthreadd守護線程,這樣才能完成用戶須要新建立線程所完成的工做。填充完結構體以後,還須要將結構體掛到全局的kthread_create_list鏈表中,而後喚醒kthreadd線程開始建立內核線程,用戶線程睡眠等待新線程建立動做的完成,新線程完成以後,設置線程的相關屬性,此時,線程就能夠完成相關的工做了。
3 內核線程處理函數
內核線程的線程處理函數並非用戶自定義的接口,而是內核實現的一個統一的接口。這個統一的接口就是kthread,而用戶自定義的線程處理函數是經過該統一接口進行回調的。Kthread接口完成了不少方面的工做,首先,就是咱們剛剛說的回調用戶自定義的接口,以便於內核線程可以完成用戶想讓該線程完成的工做。其次,就是通知kthreadd守護線程,新建立的線程已經開始啓動,khtreadd線程能夠建立下一個內核線程,第三,就是實現新建立的線程在沒有顯式調用喚醒以前,該線程是睡眠的;第四,就是能夠很方便的將新建的內核線程終止,釋放相關的資源。代碼以下所示:
static int kthread(void *_create)
{
       struct kthread_create_info *create =_create;
       int (*threadfn)(void *data);
       void *data;
       int ret = -EINTR;

       /* Copy data: it's on kthread's stack*/
       threadfn = create->threadfn;
       data = create->data;

       /* OK, tell user we're spawned, waitfor stop or wakeup */
       __set_current_state(TASK_UNINTERRUPTIBLE);
       create->result = current;
       complete(&create->started);
       schedule();
 /*線程沒有被終止,則調用用戶的回調接口完成相關的工做*/
       if (!kthread_should_stop())
                 ret = threadfn(data);

       /* It might have exited on its own, w/okthread_stop.  Check. */
/*若是用戶但願終止該線程,則通知用戶已經完成終止的準備動做*/
if(kthread_should_stop()) {
                 kthread_stop_info.err = ret;
                 complete(&kthread_stop_info.done);
       }
       return 0;
}
4 終止內核線程
內核線程的終止是經過kthread_stop接口完成的。用戶一旦調用了kthread_stop接口,首先會喚醒該內核線程,並設置須要終止的線程,而後睡眠,等待線程處理函數的喚醒。由前面的內核線程的處理函數能夠看到,就不會在執行用戶自定義的回調接口,同時會喚醒用戶線程,完成終止內核線程的工做。Kthread_stop用來trace_point來完成內核線程的清理工做,trace_point的工做原理還不清楚,姑且就認爲其能夠完成咱們須要的清理工做吧。
5 綁定內核線程到指定的cpu
對應SMP架構的CPU,能夠經過kthread_bind接口將建立的內核線程綁定到某個指定的CPU上執行。經過綁定,能夠減小內核線程在CPU之間的遷移,提升內核線程的工做效率。綁定CPU,主要原理是經過CPU的親和性來實現的。這也是LINUX調度器天生就支持的,這裏只不過是利用了調度器的親和性功能實現了內核線程的綁定功能。
6總結
內核線程在linux內核中常常被使用,瞭解linux內核線程的建立以及銷燬過程,能夠幫助咱們理解內核中利用內核線程完成的某些功能的工做原理。例如,work_queue,work_queue就是經過建立內核線程來完成相關的功能,若是咱們知道內核線程的工做原理,在利用work_queue的時候,就能作到心中有底,能夠知道何時work_queue在工做,何時在休眠等等。總之,從點滴開始學習linxu內核,能夠幫助咱們恰好的理解內核的一些功能的機制和原理。

linux

相關文章
相關標籤/搜索