一、開篇
線程是操作系統(tǒng)的重要組成部件之一,linux內(nèi)核中,內(nèi)核線程是如何創(chuàng)建的,在內(nèi)核啟動(dòng)過程中,誕生了哪些支撐整個(gè)系統(tǒng)運(yùn)轉(zhuǎn)的線程,本文將帶著這個(gè)疑問瞅一瞅內(nèi)核源碼,分析內(nèi)核線程的創(chuàng)建機(jī)制。本文基于linux內(nèi)核版本:4.1.15。
與linux內(nèi)核1號(hào)init進(jìn)程一樣,在rest_init()函數(shù)中將調(diào)用kthread_init()函數(shù)創(chuàng)建kthreadd線程,如下代碼:
pid=kernel_thread(kthreadd,NULL,CLONE_FS|CLONE_FILES);
下文將看看kthreadd()中完成了哪些事情。
二、kthreadd線程入口分析
kthreadd線程的線程入口為kthreadd(/kernel/kthread.c),如下定義:
intkthreadd(void*unused) { structtask_struct*tsk=current; //該函數(shù)會(huì)清除當(dāng)前運(yùn)行的可執(zhí)行文件的所有trace,以便啟動(dòng)一個(gè)新的trace set_task_comm(tsk,"kthreadd"); //忽略tsk的信號(hào) ignore_signals(tsk); //該行代碼允許kthreadd在任何CPU上運(yùn)行 set_cpus_allowed_ptr(tsk,cpu_all_mask); //設(shè)置由alloc_lock保護(hù)的內(nèi)存空間 set_mems_allowed(node_states[N_MEMORY]); //設(shè)置kthreadd線程不應(yīng)該被凍結(jié) current->flags|=PF_NOFREEZE; for(;;){ set_current_state(TASK_INTERRUPTIBLE); if(list_empty(&kthread_create_list)) schedule(); __set_current_state(TASK_RUNNING); spin_lock(&kthread_create_lock); while(!list_empty(&kthread_create_list)){ structkthread_create_info*create; create=list_entry(kthread_create_list.next, structkthread_create_info,list); list_del_init(&create->list); spin_unlock(&kthread_create_lock); //該一步是創(chuàng)建內(nèi)核線程的關(guān)鍵 create_kthread(create); spin_lock(&kthread_create_lock); } spin_unlock(&kthread_create_lock); } return0; }
上述第3行代碼:使用current獲取線程控制塊。current定義如下(/include/asm-generic/current.h):
#defineget_current()(current_thread_info()->task) #definecurrentget_current()
上述代碼中16~36行代碼:for(;;)是kthreadd的核心功能。使用set_current_state(TASK_INTERRUPTIBLE);將當(dāng)前線程設(shè)置為TASK_INTERRUPTIBLE狀態(tài),如果當(dāng)前沒有要?jiǎng)?chuàng)建的線程(這一步由kthread_create_list實(shí)現(xiàn)),則主動(dòng)調(diào)用schedule()執(zhí)行調(diào)度,讓出CPU,這部分由17~19行代碼實(shí)現(xiàn)。否則,kthreadd將處于喚醒狀態(tài),那么就會(huì)執(zhí)行對(duì)應(yīng)的線程創(chuàng)建操作,這部分功能由23~34行代碼實(shí)現(xiàn)。
上述代碼中,出現(xiàn)了kthread_create_list這個(gè)待創(chuàng)建線程的鏈表,定義如下:
staticLIST_HEAD(kthread_create_list);
第26~27行代碼,使用:
create=list_entry(kthread_create_list.next, structkthread_create_info,list);
從鏈表中取得 kthread_create_info 結(jié)構(gòu)的地址。
第31行代碼使用create_kthread()創(chuàng)建create代表的內(nèi)核線程。定義如下(/kernel/kernel.c):
staticvoidcreate_kthread(structkthread_create_info*create) { intpid; #ifdefCONFIG_NUMA current->pref_node_fork=create->node; #endif /*Wewantourownsignalhandler(wetakenosignalsbydefault).*/ pid=kernel_thread(kthread,create,CLONE_FS|CLONE_FILES|SIGCHLD); if(pid0)?{ ??/*?If?user?was?SIGKILLed,?I?release?the?structure.?*/ ??struct?completion?*done?=?xchg(&create->done,NULL); if(!done){ kfree(create); return; } create->result=ERR_PTR(pid); complete(done); } }
從上述代碼可知,在create_kthread()中創(chuàng)建線程同樣由kernel_thread()函數(shù)完成:
pid=kernel_thread(kthread,create,CLONE_FS|CLONE_FILES|SIGCHLD);
可見新創(chuàng)建的線程的入口是kthread,下文將繼續(xù)分析該線程函數(shù)。
三、kthread分析
該函數(shù)定義在(/kernel/kthead.c)中:
staticintkthread(void*_create) { //拷貝數(shù)據(jù) //將_create代表的kthread_create_info賦值給create structkthread_create_info*create=_create; //設(shè)置線程執(zhí)行的函數(shù)指針 int(*threadfn)(void*data)=create->threadfn; void*data=create->data; structcompletion*done; structkthreadself; intret; self.flags=0; self.data=data; init_completion(&self.exited); init_completion(&self.parked); current->vfork_done=&self.exited; /*IfuserwasSIGKILLed,Ireleasethestructure.*/ done=xchg(&create->done,NULL); if(!done){ kfree(create); do_exit(-EINTR); } /*OK,telluserwe'respawned,waitforstoporwakeup*/ /*創(chuàng)建的新的內(nèi)核線程被置為TASK_UNINTERRUPTIBLE,需要顯式的被喚醒才能運(yùn)行*/ __set_current_state(TASK_UNINTERRUPTIBLE); create->result=current; complete(done); //運(yùn)行到此處,線程已經(jīng)創(chuàng)建完畢,調(diào)用schedule執(zhí)行調(diào)度,主動(dòng)讓出CPU,喚醒的是調(diào)用kthread_create函數(shù)的進(jìn)程。 schedule(); //當(dāng)本線程(創(chuàng)建的線程)被喚醒后,將繼續(xù)執(zhí)行后續(xù)代碼 ret=-EINTR; if(!test_bit(KTHREAD_SHOULD_STOP,&self.flags)){ __kthread_parkme(&self); ret=threadfn(data); } /*wecan'tjustreturn,wemustpreserve"self"onstack*/ do_exit(ret); }
上述函數(shù)中,創(chuàng)建新 thread 的進(jìn)程恢復(fù)運(yùn)行 kthread_create() 并且返回新創(chuàng)建線程的任務(wù)描述符,在創(chuàng)建線程的線程中由于執(zhí)行了 schedule() 調(diào)度,此時(shí)并沒有執(zhí)行。需使用wake_up_process(p);喚醒新創(chuàng)建的線程,這時(shí)候新創(chuàng)建的線程才得以執(zhí)行。當(dāng)線程被喚醒后, 會(huì)接著執(zhí)行threadfn(data)(即:對(duì)應(yīng)線程的真正線程函數(shù))(這一點(diǎn)后文會(huì)通過實(shí)踐加以驗(yàn)證!):
if(!test_bit(KTHREAD_SHOULD_STOP,&self.flags)){ __kthread_parkme(&self); //執(zhí)行創(chuàng)建線程對(duì)應(yīng)的線程函數(shù),傳入的參數(shù)為data ret=threadfn(data); }
總結(jié)一下,在kthreadd線程函數(shù)中,將完成兩件重要的事情:
1、如果kthread_create_list線程創(chuàng)建鏈表為空,則調(diào)用schedule()執(zhí)行線程調(diào)度。
2、如果kthread_create_list線程創(chuàng)建鏈表不為空(即需要?jiǎng)?chuàng)建線程),則調(diào)用create_kthread()創(chuàng)建內(nèi)核線程。
(1)動(dòng)手玩一玩
基于上述分析,本小節(jié)對(duì)其加以實(shí)踐。
image-20230709113909381
重新編譯構(gòu)建內(nèi)核后啟動(dòng)運(yùn)行,在啟動(dòng)階段,會(huì)打印出下述信息,這屬于正常的預(yù)期現(xiàn)象,因?yàn)閮?nèi)核在啟動(dòng)階段會(huì)涉及到內(nèi)核重要線程的創(chuàng)建和運(yùn)行。例如:
?
接下來以模塊方式設(shè)計(jì)兩份代碼:
(1)module_1.c:使用kthread_create()創(chuàng)建一個(gè)名為my_thread的線程,在線程執(zhí)行函數(shù)中每隔1000ms打印出一行信息:my_thread running。并暴露出對(duì)my_thread線程的喚醒接口:
#include#include #include #include #include #include staticstructtask_struct*my_thread; voidwakeup_mythread(void) { //喚醒內(nèi)核線程 wake_up_process(my_thread); } EXPORT_SYMBOL(wakeup_mythread); intmy_thread_function(void*data){ while(!kthread_should_stop()){ printk("my_threadrunning "); msleep(1000); } return0; } staticint__initmodule_1_init(void){ //創(chuàng)建內(nèi)核線程 my_thread=kthread_create(my_thread_function,NULL,"my_thread"); if(IS_ERR(my_thread)){ printk(KERN_ERR"Failedtocreatekernelthread "); returnPTR_ERR(my_thread); } return0; } staticvoid__exitmodule_1_exit(void){ //停止內(nèi)核線程 kthread_stop(my_thread); } module_init(module_1_init); module_exit(module_1_exit); MODULE_AUTHOR("iriczhao"); MODULE_LICENSE("GPL");
(2)module_2.c:調(diào)用module_1的wakeup_mythread()喚醒my_thread:
#include#include #include #include externvoidwakeup_mythread(void); staticintmodule2_init(void) { printk("wakeupmythread "); wakeup_mythread(); return0; } staticvoidmodule2_exit(void) { printk("module2_exitexiting "); } module_init(module2_init); module_exit(module2_exit); //定義模塊相關(guān)信息 MODULE_AUTHOR("iriczhao"); MODULE_LICENSE("GPL");
將上述兩份代碼以模塊方式構(gòu)建。
從上圖中可見:在加載module_1的時(shí)候,打印出了:
>>>>>>>>>>>>>>>>>>>>>>>>>>kthread>>>>>>>>>>>>>>>>>>>>>>>>
則證明執(zhí)行kthread()函數(shù)創(chuàng)建了my_thread線程,但是該線程并沒有喚醒執(zhí)行,而由于在kthread()函數(shù)中調(diào)用schedule()讓出了cpu,故而后面的代碼沒有執(zhí)行。
當(dāng)在加載module_2后,則喚醒了my_thread線程,打印出了:
>>>>>>>>>>>>>>>>>>>>>>>>>>I'mrunning>>>>>>>>>>>>>>>>>>>>>>>>
然后執(zhí)行my_thread的線程函數(shù),每隔一秒打印出:
my_threadrunning
綜上,也驗(yàn)證了,當(dāng)在kthread()中調(diào)用schedule()時(shí)執(zhí)行線程調(diào)度,讓出了cpu,當(dāng)喚醒創(chuàng)建的線程時(shí),會(huì)繼續(xù)執(zhí)行schedule()后面的代碼。
四、總結(jié)與補(bǔ)充
(1)kthread_create()函數(shù)
通過以上代碼分析,可見最重要的是kthread_create_list這個(gè)全局鏈表。當(dāng)使用kthread_create()函數(shù)創(chuàng)建線程時(shí),最終都會(huì)將線程相關(guān)資源添加到kthread_create_list鏈表中。如下代碼(/include/linux/kthread.h):
#definekthread_create(threadfn,data,namefmt,arg...) kthread_create_on_node(threadfn,data,-1,namefmt,##arg)
由create_kthread()可知,通過kthread_create()入口進(jìn)來的內(nèi)核線程創(chuàng)建路徑都具有統(tǒng)一的線程函數(shù)kthread()。
而linux內(nèi)核的2號(hào)線程kthreadd正好負(fù)責(zé)內(nèi)核線程的調(diào)度和管理。所以說創(chuàng)建的內(nèi)核線程都是直接或間接的以kthreadd為父進(jìn)程。
(2)kthread_create與kernel_thread的區(qū)別
在(init/mian.c)中,1號(hào)init線程和2號(hào)kthreadd線程都是通過kernel_thread()函數(shù)創(chuàng)建的,那么kernel_thread()后續(xù)會(huì)調(diào)用do_fork()實(shí)現(xiàn)線程相關(guān)的創(chuàng)建操作。kernel_thread()函數(shù)與kthread_create()創(chuàng)建的內(nèi)核線程有什么區(qū)別呢?
1、kthread_create()創(chuàng)建的內(nèi)核線程有干凈的上下文環(huán)境,適合于驅(qū)動(dòng)模塊或用戶空間的程序創(chuàng)建內(nèi)核線程使用,不會(huì)把某些內(nèi)核信息暴露給用戶空間程序。
2、二者創(chuàng)建的進(jìn)程的父進(jìn)程不同: kthread_create()創(chuàng)建的進(jìn)程的父進(jìn)程被指定為kthreadd, 而kernel_thread()創(chuàng)建的進(jìn)程的父進(jìn)程可以是init或其他內(nèi)核線程。
(3)kthread_run()函數(shù)
kthread_run()函數(shù)用于創(chuàng)建并喚醒一個(gè)線程,其本質(zhì)上是調(diào)用kthread_create()創(chuàng)建一個(gè)線程,并使用wake_up_process()喚醒該線程。定義如下:
#definekthread_run(threadfn,data,namefmt,...) ({ structtask_struct*__k =kthread_create(threadfn,data,namefmt,##__VA_ARGS__); if(!IS_ERR(__k)) wake_up_process(__k); __k; })
(4)linux內(nèi)核創(chuàng)建線程的整體過程
綜上,linux內(nèi)核創(chuàng)建線程的整體過程為:
1、創(chuàng)建kthread_create_info結(jié)構(gòu),為其分配空間,指定線程函數(shù),線程相關(guān)描述數(shù)據(jù)等。
2、將線程的kthread_create_info結(jié)構(gòu)添加到kthread_create_list全局線程創(chuàng)建鏈表中,并喚醒2號(hào)kthreadd線程。
3、2號(hào)kthreadd線程將從kthread_create_list全局線程創(chuàng)建鏈表中取出每一個(gè)kthread_create_info結(jié)構(gòu),然后調(diào)用create_kthread()函數(shù)創(chuàng)建一個(gè)線程函數(shù)為kthread的線程。在kthread線程函數(shù)中將執(zhí)行創(chuàng)建線程指定的線程函數(shù)。
五、附錄
【附錄一】
kthread_create_on_cpu()創(chuàng)建一個(gè)綁定CPU的線程:
/** *kthread_create_on_cpu-Createacpuboundkthread *@threadfn:thefunctiontorununtilsignal_pending(current). *@data:dataptrfor@threadfn. *@cpu:Thecpuonwhichthethreadshouldbebound, *@namefmt:printf-stylenameforthethread.Formatisrestricted *to"name.*%u".Codefillsincpunumber. * *Description:Thishelperfunctioncreatesandnamesakernelthread *Thethreadwillbewokenandputintoparkmode. */ structtask_struct*kthread_create_on_cpu(int(*threadfn)(void*data), void*data,unsignedintcpu, constchar*namefmt) { structtask_struct*p; p=kthread_create_on_node(threadfn,data,cpu_to_node(cpu),namefmt, cpu); if(IS_ERR(p)) returnp; set_bit(KTHREAD_IS_PER_CPU,&to_kthread(p)->flags); to_kthread(p)->cpu=cpu; /*ParkthethreadtogetitoutofTASK_UNINTERRUPTIBLEstate*/ kthread_park(p); returnp; }
【附錄二】
kthread_create_on_node()函數(shù)將操作kthread_create_list鏈表。kthread_create_on_node()函數(shù)的功能是:創(chuàng)建kthread,并將其添加到 kthread_create_list線程創(chuàng)建鏈表中,并返回對(duì)應(yīng)的task_struct。
structtask_struct*kthread_create_on_node(int(*threadfn)(void*data), void*data,intnode, constcharnamefmt[], ...) { DECLARE_COMPLETION_ONSTACK(done); structtask_struct*task; structkthread_create_info*create=kmalloc(sizeof(*create), GFP_KERNEL); if(!create) returnERR_PTR(-ENOMEM); create->threadfn=threadfn; create->data=data; create->node=node; create->done=&done; spin_lock(&kthread_create_lock); /*注意這個(gè)全局鏈表kthread_create_list,所有通過kthread_create創(chuàng)建的內(nèi)核線程都會(huì)掛在這*/ list_add_tail(&create->list,&kthread_create_list); spin_unlock(&kthread_create_lock); /*這是最重要的地方,從代碼看是喚醒了kthreadd_task這個(gè)進(jìn)程,該進(jìn)程就是內(nèi)核中的1號(hào)進(jìn)程kthreadd 。因?yàn)閗threadd_task在rest_init()中通過find_task_by_pid_ns(pid, &init_pid_ns);進(jìn)行了linux內(nèi)核的早期賦值*/ wake_up_process(kthreadd_task); /* *Waitforcompletioninkillablestate,forImightbechosenby *theOOMkillerwhilekthreaddistryingtoallocatememoryfor *newkernelthread. */ if(unlikely(wait_for_completion_killable(&done))){ /* *IfIwasSIGKILLedbeforekthreadd(ornewkernelthread) *callscomplete(),leavethecleanupofthisstructureto *thatthread. */ if(xchg(&create->done,NULL)) returnERR_PTR(-EINTR); /* *kthreadd(ornewkernelthread)willcallcomplete() *shortly. */ wait_for_completion(&done); } task=create->result; if(!IS_ERR(task)){ staticconststructsched_paramparam={.sched_priority=0}; va_listargs; va_start(args,namefmt); vsnprintf(task->comm,sizeof(task->comm),namefmt,args); va_end(args); /* *rootmayhavechangedour(kthreadd's)priorityorCPUmask. *Thekernelthreadshouldnotinherittheseproperties. */ sched_setscheduler_nocheck(task,SCHED_NORMAL,¶m); set_cpus_allowed_ptr(task,cpu_all_mask); } kfree(create); returntask; }
kthread_create_on_node()函數(shù)的本質(zhì)則是創(chuàng)建kthread_create_info結(jié)構(gòu),并將其添加到kthread_create_list全局鏈表中(/kernel/kthread.h):
structkthread_create_info { /*從kthreadd傳遞給kthread()的信息*/ int(*threadfn)(void*data); void*data; intnode; /*從kthreadd返回給kthread_create()的結(jié)果*/ structtask_struct*result; structcompletion*done; structlist_headlist; };
審核編輯:劉清
-
Module
+關(guān)注
關(guān)注
0文章
72瀏覽量
13171 -
LINUX內(nèi)核
+關(guān)注
關(guān)注
1文章
317瀏覽量
22190 -
調(diào)度器
+關(guān)注
關(guān)注
0文章
98瀏覽量
5458
原文標(biāo)題:毛毛雨,linux內(nèi)核線程就這樣誕生了么?
文章出處:【微信號(hào):嵌入式小生,微信公眾號(hào):嵌入式小生】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
鴻蒙內(nèi)核源碼Task/線程技術(shù)分析

用戶級(jí)線程和內(nèi)核級(jí)線程
linux 下如何獲取線程ID
請(qǐng)問大佬rt-thread smart用戶程序可以創(chuàng)建實(shí)時(shí)線程么?
Linux的內(nèi)核教程
多線程編程之Linux線程編程
如何配置和使用Linux內(nèi)核printk功能
Linux內(nèi)核線程優(yōu)先級(jí)設(shè)置的方法介紹

嵌入式linux內(nèi)核開發(fā)培訓(xùn)之linux特性
linux線程淺析
為什么說內(nèi)核線程放入SCHED_FIFO的做法毫無意義?
linux內(nèi)核參數(shù)設(shè)置_linux內(nèi)核的功能有哪些

深入淺析Linux內(nèi)核之內(nèi)核線程(上)

評(píng)論