1.進(jìn)程組織結(jié)構(gòu)
1. task_struct ,thread_info 和內(nèi)核棧


在內(nèi)核中通常current宏獲取當(dāng)前正在運(yùn)行的task_struct。對(duì)于不同的硬件體系current的實(shí)現(xiàn)方式不一樣,寄存器較多的體系直接用一個(gè)寄存器來(lái)存儲(chǔ)當(dāng)前進(jìn)程的task_struct的指針,X86 current把內(nèi)核棧棧頂指針最后13位(內(nèi)核棧8KB)清零,計(jì)算出thread_info的位置,通過(guò)thread_info總task指針指向當(dāng)前進(jìn)程的task_truct
2. 進(jìn)程狀態(tài)

3. 進(jìn)程樹(shù)
進(jìn)程樹(shù)的樹(shù)根是0號(hào)進(jìn)程idle,是所有進(jìn)程的祖先。
- 0號(hào)進(jìn)程創(chuàng)建1號(hào)進(jìn)程(創(chuàng)建時(shí)是內(nèi)核線程),1號(hào)進(jìn)程負(fù)責(zé)內(nèi)核部分的初始化工作及系統(tǒng)配置,創(chuàng)建用于高速緩存和虛擬內(nèi)存管理的內(nèi)核線程
- 1號(hào)進(jìn)程調(diào)用execve()運(yùn)行磁盤(pán)上的init可執(zhí)行程序,并演變?yōu)橛脩魬B(tài)1號(hào)進(jìn)程,init進(jìn)程
- init進(jìn)程按照配置文件/etc/initab的要求,完成系統(tǒng)啟動(dòng)工作,創(chuàng)建編號(hào)為1號(hào),2號(hào),……的Getty進(jìn)程(初始化終端)
- getty進(jìn)程監(jiān)控到終端連接信號(hào)時(shí),通過(guò)調(diào)用execve()執(zhí)行l(wèi)ogin登錄程序
- 如果登錄成功過(guò),login程序通過(guò)execve()函數(shù)調(diào)用shell
- Shell進(jìn)程接收getty進(jìn)程的pid,取代原來(lái)的getty進(jìn)程。
1. 0號(hào)進(jìn)程idle
- idle進(jìn)程只能從靜態(tài)地填寫(xiě)thread_info和task_struct
- idle進(jìn)程讓CPU陷入空閑循環(huán),空閑運(yùn)行
- 多處理器上剛啟動(dòng)只有一個(gè)CPU能運(yùn)行,只有CPU0上的idle進(jìn)程完成初始化后才激活其他CPU,并通過(guò)copy_process()創(chuàng)建其他CPU的idle進(jìn)程
3. 進(jìn)程創(chuàng)建與撤銷
在Linux系統(tǒng)中,系統(tǒng)通過(guò)fork()函數(shù)復(fù)制一個(gè)現(xiàn)有的進(jìn)程創(chuàng)建一個(gè)新進(jìn)程。
接著,調(diào)用exec()函數(shù)創(chuàng)建新進(jìn)程的地址空間,并把新程序載入其中。
最終,程序通過(guò)調(diào)用exit()系統(tǒng)調(diào)用退出運(yùn)行。
進(jìn)程退出后被設(shè)置為僵死狀態(tài),直到父進(jìn)程檢查之后調(diào)用wait()在刪除進(jìn)程描述符資源
1. 進(jìn)程創(chuàng)建
1. fork ,vfork, clone
fork: 寫(xiě)時(shí)復(fù)制,賦值父進(jìn)程的task_struct,thread_info和內(nèi)核棧,mm_struct,頁(yè)表
vfork: 沒(méi)有創(chuàng)建父進(jìn)程的數(shù)據(jù)副本,子進(jìn)程與父進(jìn)程共享數(shù)據(jù)。子進(jìn)程創(chuàng)建之后,子進(jìn)程立即調(diào)用execve()加載新的進(jìn)程映象,或者與父進(jìn)程共享進(jìn)程映象。在子進(jìn)程退出之前,父進(jìn)程處于阻塞狀態(tài)。
clone: fork,vfork,__clone庫(kù)函數(shù)都是通過(guò)不同的參數(shù)調(diào)用clone()函數(shù),來(lái)實(shí)現(xiàn)進(jìn)程的創(chuàng)建的。
clone()-->do_fork()(1. copy_process() 2. wake_up_task() 3. 如果是vfork調(diào)用的,則使用wait_for_vfork_done() 等待子進(jìn)程結(jié)束或調(diào)用execve())
copy_process()
(1)復(fù)制父進(jìn)程的內(nèi)核棧,thread_info,task_struct
(2)檢查創(chuàng)建該子進(jìn)程后,沒(méi)有超出系統(tǒng)的資源限制
(3)著手將子進(jìn)程和父進(jìn)程區(qū)分開(kāi)來(lái),子進(jìn)程一些統(tǒng)計(jì)信息清空
(4)將子進(jìn)程的狀態(tài)標(biāo)志設(shè)置為TASK_UNINTERRUPTIBLE,保證子進(jìn)程不會(huì)運(yùn)行
(5)更新子進(jìn)程flag,例如設(shè)置子進(jìn)程沒(méi)有被exec調(diào)用的標(biāo)志,超級(jí)用戶權(quán)限標(biāo)志清零
(6)根據(jù)傳遞進(jìn)clone的參數(shù),子進(jìn)程拷貝或共享父進(jìn)程地址空間,打開(kāi)的文件,文件系統(tǒng)信息等
(7)返回一個(gè)指向子進(jìn)程的指針
wake_up_task()
將子進(jìn)程添加到調(diào)度隊(duì)列上等待調(diào)度獲取CPU獲取執(zhí)行。
創(chuàng)建線程:

線程的用戶態(tài)堆棧,task_struct,內(nèi)核棧和thread_info是私有的
創(chuàng)建進(jìn)程和寫(xiě)時(shí)復(fù)制:

2. execve()系統(tǒng)調(diào)用
前面進(jìn)程只是創(chuàng)建了一個(gè)與父進(jìn)程大體一致的子進(jìn)程(僅僅是資源共享上的區(qū)別)。execve使用指定可執(zhí)行文件替換現(xiàn)有進(jìn)程上的進(jìn)程印象。
舉一個(gè)例子:在Shell中運(yùn)行一個(gè)二進(jìn)制程序。
(1)Shell進(jìn)程將參數(shù) 運(yùn)行時(shí)環(huán)境添加到自己的用戶態(tài)堆棧中
(2)Shell使用fork()創(chuàng)建一個(gè)子進(jìn)程,放到CPU調(diào)度隊(duì)列上。
(3)子進(jìn)程執(zhí)行execve()使用二進(jìn)制程序替換原來(lái)的Shell映象并將EIP指向新程序的入口開(kāi)始執(zhí)行。
execve():
(1)execve()--》sys_execve()--》do_execve()——》do_execve_common()
(2)do_execve_common()執(zhí)行:
(1) unshared_files()為新進(jìn)程復(fù)制一份打開(kāi)的文件表
(2) kazlloc()分配一個(gè)可執(zhí)行文件的結(jié)構(gòu)體linux_binprm,將可執(zhí)行文件運(yùn)行時(shí)所要用的參數(shù),運(yùn)行時(shí)環(huán)境等信息添加到linux_binprm。
(3)open_exec() 打開(kāi)可執(zhí)行文件。具體參考文件系統(tǒng)
(4) 找到負(fù)載最小的CPU。
(5)填寫(xiě)linux_binprm實(shí)例,創(chuàng)建進(jìn)程映象,將參數(shù)傳入新進(jìn)程的用戶態(tài)堆棧。
(6)執(zhí)行新程序,使用過(guò)load_elf_binary()執(zhí)行處理ELF的函數(shù)。
(7)如果該程序是靜態(tài)鏈接的,則load_elf_binary()直接返回新程序的入口地址,并調(diào)用start_thread()將EIP的值置為新程序的入口地址。
2.進(jìn)程撤銷
進(jìn)程要終結(jié)的時(shí)候會(huì)調(diào)用exit()函數(shù),最終通過(guò)調(diào)用do_exit()這個(gè)函數(shù)來(lái)結(jié)束進(jìn)程的。
(1)簡(jiǎn)單的說(shuō),就是將各個(gè)引用計(jì)數(shù)器減一,若有計(jì)數(shù)器歸0,則釋放相應(yīng)的內(nèi)存。
(2)然后調(diào)用exit_notify()告知父進(jìn)程為子進(jìn)程找養(yǎng)父,并把進(jìn)程狀態(tài)設(shè)置為EXIT_ZOMBIE.最后調(diào)用schedule()切換到其他進(jìn)程。
(3)自此該進(jìn)程不會(huì)被再次調(diào)用。此時(shí)該進(jìn)程占用的內(nèi)存僅為內(nèi)存棧,thread_info和task_struct。
(4)當(dāng)父進(jìn)程調(diào)用wait或waitpid檢測(cè)到僵尸進(jìn)程提供的信息之后,將進(jìn)程所持有的剩余內(nèi)存釋放。至此進(jìn)程終結(jié)。wait掛起當(dāng)前進(jìn)程知道一個(gè)子進(jìn)程退出。
1.孤兒進(jìn)程,僵尸進(jìn)程
孤兒進(jìn)程: 一個(gè)父進(jìn)程退出,而它的一個(gè)或多個(gè)子進(jìn)程還在運(yùn)行,那么那些子進(jìn)程將成為孤兒進(jìn)程。孤兒進(jìn)程將被init進(jìn)程(進(jìn)程號(hào)為1)所收養(yǎng),并由init進(jìn)程對(duì)它們完成狀態(tài)收集工作。
僵尸進(jìn)程: 一個(gè)進(jìn)程使用fork創(chuàng)建子進(jìn)程,如果子進(jìn)程退出,而父進(jìn)程并沒(méi)有調(diào)用wait或waitpid獲取子進(jìn)程的狀態(tài)信息,那么子進(jìn)程的進(jìn)程描述符仍然保存在系統(tǒng)中。這種進(jìn)程稱之為僵死進(jìn)程。如果進(jìn)程不調(diào)用wait / waitpid的話, 那么保留的那段信息就不會(huì)釋放,其進(jìn)程號(hào)就會(huì)一直被占用,但是系統(tǒng)所能使用的進(jìn)程號(hào)是有限的,如果大量的產(chǎn)生僵死進(jìn)程,將因?yàn)闆](méi)有可用的進(jìn)程號(hào)而導(dǎo)致系統(tǒng)不能產(chǎn)生新的進(jìn)程. 此即為僵尸進(jìn)程的危害,應(yīng)當(dāng)避免。
4. 進(jìn)程切換
進(jìn)程切換主要包括:1.當(dāng)前進(jìn)程信息壓入內(nèi)核棧 2. 切換CPU頁(yè)表寄存器 3.切入進(jìn)程工作現(xiàn)場(chǎng)恢復(fù)
進(jìn)程切換是通過(guò)schedule()函數(shù)完成的。schedule中最核心的就是switch_mm()切換進(jìn)程映象,內(nèi)核堆棧和硬件上下文的切換switch_to()
1. 頁(yè)表切換
switch_mm() load_cr3(next->pgd)

2.內(nèi)核堆棧切換
每個(gè)進(jìn)程的內(nèi)核空間都是一樣的,因此,盡管在switch_mm()中切換了頁(yè)表但是對(duì)switch_to沒(méi)有影響,所以說(shuō)switch_to是平滑的。

switch_to(pre,next,last)
pre:當(dāng)前進(jìn)程 next:下一個(gè)進(jìn)程 last:上一個(gè)進(jìn)程
(a)頁(yè)表切換后內(nèi)核空間分布狀況
(b)將當(dāng)前進(jìn)程的內(nèi)核棧棧底和進(jìn)程狀態(tài)信息保存在內(nèi)核棧中。
(c)將當(dāng)前進(jìn)程的棧頂ESP保存在prev->thread.sp中,將ESP = next->thread.sp,此時(shí)完成了內(nèi)核切換,同時(shí)current宏根據(jù)P1的ESP找到P1的thread_info,進(jìn)而找到task_struct。此時(shí),內(nèi)核狀態(tài)為(d)
(e)將P0的EIP保存到pre->thread.ip 此時(shí)狀態(tài)為(f)
(g)執(zhí)行__switch_to()完成一些浮點(diǎn)部件現(xiàn)場(chǎng)保留和恢復(fù)
(i)EIP=next->thread.ip,繼續(xù)執(zhí)行P1上次schedule()被打斷的指令,在通過(guò)系統(tǒng)調(diào)用返回返回到用戶空間執(zhí)行代碼。其中,內(nèi)核態(tài)和用戶態(tài)切換是通過(guò)任務(wù)狀態(tài)段TSS,來(lái)獲取內(nèi)核或用戶態(tài)的堆棧地址的。
5. 進(jìn)程調(diào)度
1. 基本框架
實(shí)時(shí)任務(wù)調(diào)度算法:FIFO和RR。普通任務(wù)完全公平調(diào)度算法

四個(gè)調(diào)度類優(yōu)先級(jí):stop_sched_class,rt_sched_class, fair_sched_class,idle_sched_class,NUll
運(yùn)行隊(duì)列:


put_prev_task():通知當(dāng)前進(jìn)程準(zhǔn)備調(diào)出
pick_next_task()選擇要調(diào)入的進(jìn)程
2. CFS
CFS為每一個(gè)進(jìn)程設(shè)置一個(gè)虛擬時(shí)間,調(diào)度時(shí)總是選擇虛擬時(shí)間推進(jìn)緩慢的進(jìn)程先運(yùn)行。優(yōu)先級(jí)越高,虛擬時(shí)間推進(jìn)越慢,所占用CPU時(shí)間就越高。
虛擬時(shí)間推進(jìn):

內(nèi)核中并不直接使用進(jìn)程的優(yōu)先級(jí)數(shù)據(jù)值,而是將優(yōu)先級(jí)轉(zhuǎn)換為權(quán)重計(jì)算虛擬時(shí)間的推進(jìn)。任務(wù)的nice值每降低1,則多獲得10%的CPU時(shí)間。
挑選下一個(gè)任務(wù):普通任務(wù)的運(yùn)行隊(duì)列的結(jié)構(gòu)是紅黑樹(shù)。每次挑選紅黑樹(shù)最左側(cè)的葉子節(jié)點(diǎn),將葉子節(jié)點(diǎn)緩存起來(lái),每次挑選時(shí),只要從這個(gè)緩存區(qū)取出該進(jìn)程就行,如果該緩存區(qū)為空,則調(diào)用idle進(jìn)程。
調(diào)度延遲:CFS為無(wú)線小調(diào)度周期設(shè)置了一個(gè)“目標(biāo)延遲”默認(rèn)是20ms,即保證每個(gè)可運(yùn)行的進(jìn)程都應(yīng)該至少運(yùn)行一次的某個(gè)時(shí)間間隔.。越小的周期帶來(lái)越好的交互性,同時(shí)也接近完美。如果揮動(dòng)進(jìn)程的數(shù)目超過(guò)該上限, 則延遲周期也成比例的線性擴(kuò)展.。周期長(zhǎng)度是
__sched_period = sysctl_sched_latency * nr_running / sched_nr_latency
3.實(shí)時(shí)策略
FIFO:不適用時(shí)間片,可以一直運(yùn)行下去,除非有更高優(yōu)先級(jí)的任務(wù)搶占
RR:是帶有時(shí)間片的FIFO,只能在同一優(yōu)先級(jí)的任務(wù)中輪詢。
6 其他
1 進(jìn)程和線程區(qū)別
- 進(jìn)程是資源分配的基本單位,線程是調(diào)度的基本單位。
- 實(shí)體間(進(jìn)程間,線程間,進(jìn)線程間)通信方式的不同
進(jìn)程:A.共享內(nèi)存 B.消息隊(duì)列 C.信號(hào)量 D.有名管道 E.無(wú)名管道 F.信號(hào) G.文件 H.socket
線程: 線程間的通信方式上述進(jìn)程間的方式都可沿用,且還有自己獨(dú)特的幾種:A.互斥量 B.自旋鎖 C.條件變量 D.讀寫(xiě)鎖 E.線程信號(hào) G.全局變量 - 進(jìn)程有父子關(guān)系,線程只有一個(gè)父線程,其他都為子線程
多線程的優(yōu)勢(shì):
- 理由之一是和進(jìn)程相比,它是一種非常"節(jié)儉"的多任務(wù)操作方式。我們知道,在Linux系統(tǒng)下,啟動(dòng)一個(gè)新的進(jìn)程必須分配給它獨(dú)立的地址空間,建立眾多的數(shù)據(jù)表來(lái)維護(hù)它的代碼段、堆棧段和數(shù)據(jù)段,這是一種"昂貴"的多任務(wù)工作方式。而運(yùn)行于一個(gè)進(jìn)程中的多個(gè)線程,它們彼此之間使用相同的地址空間,共享大部分?jǐn)?shù)據(jù),啟動(dòng)一個(gè)線程所花費(fèi)的空間遠(yuǎn)遠(yuǎn)小于啟動(dòng)一個(gè)進(jìn)程所花費(fèi)的空間,而且,線程間彼此切換所需的時(shí)間也遠(yuǎn)遠(yuǎn)小于進(jìn)程間切換所需要的時(shí)間。
- 理由之二是線程間方便的通信機(jī)制。對(duì)不同進(jìn)程來(lái)說(shuō),它們具有獨(dú)立的數(shù)據(jù)空間,要進(jìn)行數(shù)據(jù)的傳遞只能通過(guò)通信的方式進(jìn)行,這種方式不僅費(fèi)時(shí),而且很不方便。線程則不然,由于同一進(jìn)程下的線程之間共享數(shù)據(jù)空間,所以一個(gè)線程的數(shù)據(jù)可以直接為其它線程所用,這不僅快捷,而且方便。
- 提高應(yīng)用程序響應(yīng)。這對(duì)圖形界面的程序尤其有意義,當(dāng)一個(gè)操作耗時(shí)很長(zhǎng)時(shí),整個(gè)系統(tǒng)都會(huì)等待這個(gè)操作,此時(shí)程序不會(huì)響應(yīng)鍵盤(pán)、鼠標(biāo)、菜單的操作,而使用多線程技術(shù),將耗時(shí)長(zhǎng)的操作(time consuming)置于一個(gè)新的線程,可以避免這種尷尬的情況。
- 使多CPU系統(tǒng)更加有效。操作系統(tǒng)會(huì)保證當(dāng)線程數(shù)不大于CPU數(shù)目時(shí),不同的線程運(yùn)行于不同的CPU上。
- 改善程序結(jié)構(gòu)。一個(gè)既長(zhǎng)又復(fù)雜的進(jìn)程可以考慮分為多個(gè)線程,成為幾個(gè)獨(dú)立或半獨(dú)立的運(yùn)行部分,這樣的程序會(huì)利于理解和修改。
2 用戶線程 內(nèi)核線程
兩種類型的內(nèi)核線程:
線程按周期性間隔運(yùn)行,檢測(cè)特定資源的使用,在用量超出或者低于預(yù)置的限制時(shí)采取行動(dòng)
在線程啟動(dòng)后則一直等待,直到內(nèi)核線程請(qǐng)求執(zhí)行某一特定的操作。
不同之處在于:
內(nèi)核線程只工作在內(nèi)核態(tài)中;而用戶線程則既可以運(yùn)行在內(nèi)核態(tài)(執(zhí)行系統(tǒng)調(diào)用時(shí)),也可以運(yùn)行在用戶態(tài);
內(nèi)核線程沒(méi)有用戶空間,所以對(duì)于一個(gè)內(nèi)核線程來(lái)說(shuō),它的0 ~ 3G的內(nèi)存空間是空白的,它的current->mm是空的,與內(nèi)核使用同一張頁(yè)表;而用戶線程則可以看到完整的0~4G內(nèi)存空間。
3.進(jìn)程狀態(tài)
