国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計 > 正文

linux內(nèi)核線程 [創(chuàng)建]

2019-11-14 09:57:57
字體:
供稿:網(wǎng)友

本文以ARM架構(gòu)為例,講解linux的內(nèi)核線程是如何創(chuàng)建的。

Linux內(nèi)核在完成初始之后,會把控制權(quán)交給應(yīng)用程序。只有當(dāng)硬件中斷、軟中斷、異常等發(fā)生時,CPU才會從用戶空間切換到內(nèi)核空間來執(zhí)行相應(yīng)的處理,完成后又回來用戶空間。

如果內(nèi)核需要周期性地做一些事情(比如頁面的換入換出,磁盤高速緩存的刷新等),又該怎么辦呢?內(nèi)核線程(內(nèi)核進程)可以解決這個問題。

內(nèi)核線程(kernel thread)是由內(nèi)核自己創(chuàng)建的線程,也叫做守護線程(deamon)。在終端上用命令”ps -Al”列出的所有進程中,名字以k開關(guān)以d結(jié)尾的往往都是內(nèi)核線程,比如kthreadd、kswapd。

內(nèi)核線程與用戶線程的相同點是:

都由do_fork()創(chuàng)建,每個線程都有獨立的task_struct和內(nèi)核棧;都參與調(diào)度,內(nèi)核線程也有優(yōu)先級,會被調(diào)度器平等地?fù)Q入換出。

不同之處在于:

內(nèi)核線程只工作在內(nèi)核態(tài)中;而用戶線程則既可以運行在內(nèi)核態(tài),也可以運行在用戶態(tài);內(nèi)核線程沒有用戶空間,所以對于一個內(nèi)核線程來說,它的0~3G的內(nèi)存空間是空白的,它的current->mm是空的,與內(nèi)核使用同一張頁表;而用戶線程則可以看到完整的0~4G內(nèi)存空間。

在Linux內(nèi)核啟動的最后階段,系統(tǒng)會創(chuàng)建兩個內(nèi)核線程,一個是init,一個是kthreadd。其中init線程的作用是運行文件系統(tǒng)上的一系列”init”腳本,并啟動shell進程,所以init線程稱得上是系統(tǒng)中所有用戶進程的祖先,它的pid是1。kthreadd線程是內(nèi)核的守護線程,在內(nèi)核正常工作時,它永遠(yuǎn)不退出,是一個死循環(huán),它的pid是2。

內(nèi)核初始化工作的最后一部分是在函數(shù)rest_init()中完成的。在這個函數(shù)中,主要做了4件事情,分別是:創(chuàng)建init線程,創(chuàng)建kthreadd線程,執(zhí)行schedule()開始調(diào)度,執(zhí)行cpu_idle()讓CPU進入idle狀態(tài)。經(jīng)過簡化的代碼如下:

12345678staticnoinline void__init_refok rest_init(void)__releases(kernel_lock){kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);schedule();cpu_idle();}

內(nèi)核線程的創(chuàng)建過程比較曲折,讓我們一步一步來看。

創(chuàng)建內(nèi)核線程的入口函數(shù)是kernel_thread,定義如下:

123456789101112131415pid_t kernel_thread(int(*fn)(void *), void *arg, unsignedlong flags){structpt_regs regs;memset(&regs, 0,sizeof(regs));regs.ARM_r4 = (unsignedlong)arg;regs.ARM_r5 = (unsignedlong)fn;regs.ARM_r6 = (unsignedlong)kernel_thread_exit;regs.ARM_r7 = SVC_MODE | PSR_ENDSTATE | PSR_ISETSTATE;regs.ARM_pc = (unsignedlong)kernel_thread_helper;regs.ARM_cpsr = regs.ARM_r7 | PSR_I_BIT;returndo_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, &regs, 0, NULL, NULL);}

它的第一個參數(shù)是線程所要執(zhí)行的函數(shù)的指針,第二個參數(shù)是線程的參數(shù),第三個是線程屬性。

在kernel_thread()函數(shù)中先是準(zhǔn)備一些寄存器的值,并保存起來。然后執(zhí)行了do_fork()來復(fù)制task_struct內(nèi)容,并建立起自己的內(nèi)核棧。在kernel_thread() > do_fork() > copy_PRocess() > copy_thread()函數(shù)調(diào)用中,有一個很重要的操作需要留意一下:

123456789101112intcopy_thread(unsignedlong clone_flags, unsignedlong stack_start,unsignedlong stk_sz, struct task_struct *p,struct pt_regs *regs){structthread_info *thread= task_thread_info(p);......memset(&thread->cpu_context, 0,sizeof(structcpu_context_save));thread->cpu_context.sp = (unsignedlong)childregs;thread->cpu_context.pc = (unsignedlong)ret_from_fork;......return0;}

注意這里把cpu_context中保存的pc寄存器值設(shè)為ret_from_fork函數(shù)的地址,這在后面調(diào)度的時候會用到。

注:前面的這兩段代碼中都有設(shè)置pc寄存器,但是所設(shè)的內(nèi)容是不同的:在kernel_thread()中設(shè)置的regs.ARM_*值最后會被壓入內(nèi)核棧,是在context_switch完成之后待要運行的目標(biāo)代碼;而在copy_thread()中設(shè)置的sp和pc則是thread_info結(jié)構(gòu)中cpu_context的值,是在context_switch()過程中要用的。

rest_init()中兩次調(diào)用過kernel_thread()之后,就分別創(chuàng)建好了init和kthreadd內(nèi)核線程的運行上下文,并已經(jīng)加入了運行隊列,隨時可以運行了。

接下來在schedule()里面最終會運行到switch_to()做上下文切換,這個函數(shù)的實現(xiàn)細(xì)節(jié)在此前的文章中已經(jīng)講過,不再贅述,這里只說我們的場景。在switch_to()完成之后,新線程的sp寄存器已經(jīng)切換到線程自己的棧上,新線程的pc則成了ret_from_fork。

接下來新線程就跳轉(zhuǎn)到ret_from_fork()函數(shù)繼續(xù)執(zhí)行。ret_from_fork()是用匯編代碼來寫的,用于fork系統(tǒng)調(diào)用(軟中斷)完成后的收尾工作。中斷的收尾工作最后都會要完成一件事情,就是恢復(fù)原先運行的“用戶”程序狀態(tài),即彈出設(shè)置內(nèi)核棧上所保存的各寄存器值。而我們此前保存在這里的pc寄存器指向的是函數(shù)kernel_thread_helper()的地址,這個函數(shù)是用匯編寫的:

1234567891011externvoid kernel_thread_helper(void);asm( ".pushsection .text/n"" .align/n"" .type kernel_thread_helper, #function/n""kernel_thread_helper:/n"" 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");

這段代碼把pc值設(shè)為r5,在kernel_thread()中我們已經(jīng)把r5設(shè)為線程的目標(biāo)函數(shù)的值,而返回地址寄存器lr被設(shè)為r6,即此前設(shè)置的kernel_thread_exit()函數(shù)地址。

所以,接下來內(nèi)核線程將會被正式啟動,如果線程退出(即線程函數(shù)運行結(jié)束)的話,kernel_thread_exit()會做掃尾工作。

到這里,我們已經(jīng)講完了內(nèi)核線程啟動的整個過程。

 

最后我們看一下剛剛啟動起來的兩個內(nèi)核線程都做了哪些事情:

init線程:

12345678910111213141516staticint __init kernel_init(void* unused){......init_post();}staticnoinline intinit_post(void) __releases(kernel_lock){......run_init_process("/sbin/init");run_init_process("/etc/init");run_init_process("/bin/init");run_init_process("/bin/sh");panic("No init found. Try passing init= option to kernel. ""See Linux Documentation/init.txt for guidance.");}

在init線程中,將運行完”/sbin/init”、”/etc/init”和”/bin/init”三個腳本,并啟動shell。run_init_process(“/bin/sh”)并不會返回,init線程就停在這里,以后所有的應(yīng)用程序進程都將從/bin/sh克隆,而sh來自init內(nèi)核線程,所以init線程最終成為所有用戶進程的祖先。

kthreadd線程:

1234567891011int kthreadd(void *unused){for(;;) {if(list_empty(&kthread_create_list))schedule();while(!list_empty(&kthread_create_list)) {create_kthread(create);}}return0;}

可見,在每一次循環(huán)里kthreadd只做兩件事:如果有其它的內(nèi)核線程需要創(chuàng)建,就調(diào)用create_kthread()來逐個創(chuàng)建;如果沒有就調(diào)用schedule()把自己換出CPU,讓別的線程進來運行。

在內(nèi)核線程創(chuàng)建過程中還有兩個有趣的細(xì)節(jié)值得說一下:

雖然init線程是在kthreadd之前創(chuàng)建的,pid也比較小,但是在schedule()的時候,最先被選中先運行的是kthreadd。這不會有任何影響,因為kthreadd總會讓出CPU,init線程一定能啟動。進程號PID的分配是從0開始的,但是在”ps”命令中看不到0號進程。這是因為0號pid被分給了“啟動”內(nèi)核進程,就是完成了系統(tǒng)引導(dǎo)工作的那個進程。在函數(shù)rest_init()中,0號進程在創(chuàng)建完成了init和kthreadd兩個內(nèi)核線程之后,調(diào)用schedule()使得pid=1和2的兩個線程得以啟動,但是pid=0的線程并不參與調(diào)度,所以這個進程就再也得不到運行了。如下所示,在我們前面已經(jīng)看到過的這段代碼中,schedule()不會返回,最后一行的cpu_idle()其實是不會被運行到的:
12345678staticnoinline void__init_refok rest_init(void)__releases(kernel_lock){kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);schedule();<del>cpu_idle();</del>}

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 奈曼旗| 邛崃市| 西城区| 青浦区| 珲春市| 博白县| 重庆市| 汉阴县| 监利县| 甘肃省| 柘荣县| 滨州市| 化州市| 甘洛县| 三河市| 台中县| 乐亭县| 松溪县| 中江县| 通许县| 奉贤区| 晴隆县| 沛县| 和硕县| 沙湾县| 连州市| 太谷县| 岗巴县| 宁都县| 巴中市| 洛扎县| 濉溪县| 济宁市| 绥江县| 顺平县| 手机| 荆门市| 托里县| 南和县| 灵川县| 潼关县|