1. 前文鋪墊
進程狀態(tài)是task_struct內(nèi)的一個整數(shù);進行:進程在調(diào)度隊列中,進程的狀態(tài)都是running,阻塞:等待某種設(shè)備或者資源就緒。進程是一個隊列,設(shè)備也是一個隊列,當(dāng)我們讀磁盤,讀網(wǎng)卡的時候,如果對應(yīng)設(shè)備未就緒那么進程就要阻塞等待了。進程狀態(tài)變化的表現(xiàn)之一就是要在不同的隊列中進行流動,本質(zhì)都是數(shù)據(jù)結(jié)構(gòu)的增刪查改!
理解內(nèi)核鏈表

如果一個類里面有多個next,prve,那么就可以把任何一個task_struct即屬于運行隊列,又屬于全局鏈表,還可以把它放到二叉樹中等。

2. 進程狀態(tài)
一個進程可以有幾個狀態(tài)(在Linux內(nèi)核里,進程有時候也叫做任務(wù))。
下面的狀態(tài)在kernel源代碼里定義:代碼語言:JavaScript代碼運行次數(shù):0運行復(fù)制
/**The task state array is a strange "bitmap" of*reasons to sleep. Thus "running" is zero, and*you can test for combinations of others with*simple bit tests.*/static const char *const task_state_array[] = { "R (running)", /*0 */ "S (sleeping)", /*1 */ "D (disk sleep)", /*2 */ "T (stopped)", /*4 */ "t (tracing stop)", /*8 */ "X (dead)", /*16 */ "Z (zombie)", /*32 */};
R 運行或可運行 (Running 或 Runnable)狀態(tài)描述: 進程正在CPU上執(zhí)行,或在運行隊列中等待調(diào)度。觸發(fā)場景: 進程處于活動狀態(tài),正在執(zhí)行或準(zhǔn)備執(zhí)行。S 可中斷睡眠(Interruptible Sleep)狀態(tài)描述: 進程在等待事件完成(如I/O操作、信號),可被信號中斷。觸發(fā)場景: 例如調(diào)用 sleep()、read() 等阻塞操作時。D 不可中斷睡眠(Uninterruptible Sleep)狀態(tài)描述: 進程等待不可中斷的操作(如硬件I/O),不響應(yīng)信號。觸發(fā)場景: 常見于磁盤I/O或某些內(nèi)核操作,需等待操作完成。T 停止(Stopped)狀態(tài)描述: 進程被信號(如 SigsTOP、SIGTSTP)暫停,需 SIGCONT 恢復(fù)。觸發(fā)場景: 手動暫停進程(如按 Ctrl+Z)或調(diào)試時。Z 僵尸(Zombie)狀態(tài)描述: 進程已終止,但父進程未調(diào)用 wait() 回收資源。觸發(fā)場景: 父進程未正確處理子進程退出,導(dǎo)致殘留進程描述符。t 追蹤狀態(tài)(Tracing Stop)狀態(tài)描述: 進程被調(diào)試器(如 gdb)跟蹤時暫停,屬于停止?fàn)顟B(tài)的一種。觸發(fā)場景: 調(diào)試器設(shè)置斷點或單步執(zhí)行時。X 死亡(Dead)狀態(tài)描述: 子進程結(jié)束之后,父進程獲取子進程信息之前。觸發(fā)場景: 父進程已回收子進程狀態(tài),短暫存在后消失。2.1 進程狀態(tài)查看
2.2 僵尸進程

僵死狀態(tài)(Zombies)是一個比較特殊的狀態(tài)。當(dāng)進程退出并且父進程(使用wait()系統(tǒng)調(diào)用)沒有讀取到子進程退出的返回代碼時就會產(chǎn)生僵死(尸)進程。僵死進程會以終止?fàn)顟B(tài)保持在進程表中,并且會一直在等待父進程讀取退出狀態(tài)代碼。只要子進程退出,父進程還在運行,但父進程沒有讀取子進程狀態(tài),子進程進入Z狀態(tài)。2.3 僵尸進程危害進程的退出狀態(tài)必須被維持下去,因為他要告訴關(guān)心它的進程(父進程),你交給我的任務(wù),我 辦的怎么樣了。可父進程如果一直不讀取,那子進程就一直處于Z狀態(tài)?是的!維護退出狀態(tài)本身就是要用數(shù)據(jù)維護,也屬于進程基本信息,所以保存在task_struct(PCB)中,換句話說,Z狀態(tài)?直不退出,PCB一直都要維護?是的!那一個父進程創(chuàng)建了很多子進程,就是不回收,是不是就會造成內(nèi)存資源的浪費?是的!因為數(shù)據(jù)結(jié)構(gòu)對象本身就要占用內(nèi)存,c語言中定義一個結(jié)構(gòu)體變量(對象),是要在內(nèi)存的某個位置進行開辟空間,那就會存在內(nèi)存泄漏?是的! 如何避免呢?我們后期講。2.4 孤兒進程我們先來創(chuàng)建一段代碼

代碼運行后,子進程一直運行,父進程運行5秒后退出

這個1號進程是誰呢?top一下,我們可以看到它是systemd

我們繼續(xù)查一下這個systemd

為什么子進程會被1(systemd)號進程領(lǐng)養(yǎng)呢?如果不領(lǐng)養(yǎng)會出現(xiàn)什么問題呢? 答案是如果不被領(lǐng)養(yǎng),那么這個子進程就進入僵尸進程,有可能會造成內(nèi)存泄漏父進程為什么不會變成孤兒進程或者僵尸進程呢? 答案是父進程也有自己的父進程,父進程的父進程就是bash一旦進程變成孤兒進程,它就會被1號進程領(lǐng)養(yǎng),變成后臺進程,這時候Ctrl c就殺不掉它了,我們只能使用kill來殺死。3. 進程優(yōu)先級3.1 概念cpu資源分配的先后順序,就是指進程的優(yōu)先級(priority)。目標(biāo)資源稀缺,導(dǎo)致要通過優(yōu)先級確認誰先誰后的問題。優(yōu)先權(quán)高的進程有優(yōu)先執(zhí)行權(quán)利。配置進程優(yōu)先級對多任務(wù)環(huán)境的linux很有用,可以改善系統(tǒng)性能。還可以把進程運行到指定的CPU上,這樣一來,把不重要的進程安排到某個CPU,可以大大改善系統(tǒng)整體性能。優(yōu)先級 vs 權(quán)限:優(yōu)先級是能得到資源,先后的問題,權(quán)限是能否得到資源的問題
??優(yōu)先級其實也是一種數(shù)字,是task_struct中的一種屬性,數(shù)字值越低,優(yōu)先級越高 ,基于時間片的分時操作系統(tǒng),優(yōu)先級未來可能變化,但變化的幅度不能太大
3.2 查看系統(tǒng)進程命令ps -al,其中a表示所有,l表示詳細信息。我們上上面代碼中的父進程不再退出,父子進程一直運行,再運行代碼

在linux系統(tǒng)中,每個用戶都有一個UID,Linux中識別用戶就是用UID識別的。
UID:代表執(zhí)行者的身份PID:代表這個進程的代號PPID:代表這個進程是由哪個進程發(fā)展衍生出來的,亦即父進程的代號PRI:代表這個進程可被執(zhí)行的優(yōu)先級,其值越小越早被執(zhí)行。進程優(yōu)先級默認:80NI:代表這個進程優(yōu)先級的修正數(shù)據(jù),nice值 ??進程真實的優(yōu)先級 = PRI(默認) + NI ??PRI(new) = PRI(old) + nice所以,調(diào)整進程優(yōu)先級,在Linux下,就是調(diào)整進程nice值nice其取值范圍是-20至19,一共40個級別。linux進程優(yōu)先級范圍[60,99]查UID,命令ls -ln

sp用戶對應(yīng)的UID就是1001,文件創(chuàng)建的時候會把這個UID保存起來表明這個文件是誰創(chuàng)建的,進程創(chuàng)建的時候也會把UID保存起來表明進程是誰創(chuàng)建的。 ? 所以當(dāng)我們訪問一個文件時,系統(tǒng)怎么識別出我們是擁有者,所屬組,或者other呢,我們訪問文件時本質(zhì)就是進程在訪問文件,進程怎么知道我們是誰呢?答案是是誰啟動的這個進程,進程就知道這個人的UID,這個文件是誰創(chuàng)建的這個文件的UID就有了,所以一個進程將來拿著它的UID和文件的UID做對比,相等了就是擁有者,不相等查下一個,兩個都不相等就是 other。 ??linux系統(tǒng)中,訪問任何資源都是進程訪問,進程就代表用戶。
3.3 查看進程優(yōu)先級的命令
用top命令更改已存在進程的nice:
top 進入top后按“r”?>輸入進程PID?>輸入nice值 其他調(diào)整優(yōu)先級的命令:nice,renice linux調(diào)整優(yōu)先級的系統(tǒng)調(diào)用 3.4 補充概念-競爭、獨立、并行、并發(fā)競爭性: 系統(tǒng)進程數(shù)目眾多,而CPU資源只有少量,甚至1個,所以進程之間是具有競爭屬性的。為了高效完成任務(wù),更合理競爭相關(guān)資源,便具有了優(yōu)先級獨立性: 多進程運行,需要獨享各種資源,多進程運行期間互不干擾并行: 多個進程在多個CPU下分別,同時進行運行,這稱之為并行并發(fā): 多個進程在?個CPU下采用進程切換的方式,在一段時間之內(nèi),讓多個進程都得以推進,稱之為并發(fā)

4. 進程切換
先談兩個問題:
死循環(huán)進程是如何運行的 我們平時在vs中寫一個while(1)的死循環(huán),一旦跑起來我們就會發(fā)現(xiàn)系統(tǒng)會變卡了,但是不會卡死。 a.一旦一個進程占有CPU,會把自己的代碼跑完嗎?不會!(除非這個代碼很短)每個進程系統(tǒng)都會為它分配一個叫做時間片的東西。所以每一個進程擁有CPU資源都不是永久性的,而是臨時性的。 b.死循環(huán)進程不會打死進程,因為死循環(huán)進程不會一直占用CPU!cpu,寄存器 cpu執(zhí)行一個進程的時候就和PCB的關(guān)系不大了,cpu重點是訪問的是進程的代碼和數(shù)據(jù),所以cpu會訪問當(dāng)前進程的代碼和數(shù)據(jù),為了能夠處理一條一條的代碼和數(shù)據(jù),所以cpu中會存在很多的寄存器,每個寄存器在cpu內(nèi)部都有著臨時保存數(shù)據(jù)的任務(wù),所以當(dāng)進程再跑時,寄存器就會被填上臨時值,有的是計算結(jié)果,浮點數(shù)計算有沒有錯誤等。 結(jié)論: a.寄存器就是cpu內(nèi)部的臨時空間 b. 寄存器 != 寄存器里面的數(shù)據(jù)
進程如何切換? CPU上下文切換:其實際含義是任務(wù)切換,或者CPU寄存器切換。當(dāng)多任務(wù)內(nèi)核決定運行另外的任務(wù)時,它保存正在運行任務(wù)的當(dāng)前狀態(tài),也就是CPU寄存器中的全部內(nèi)容。這些內(nèi)容被保存在任務(wù)自己的堆棧中,入棧工作完成后就把下一個將要運行的任務(wù)的當(dāng)前狀況從該任務(wù)的棧中重新裝入CPU寄存器,并開始下一個任務(wù)的運行,這一過程就是context switch。

進程切換最核心的就是保存和恢復(fù)當(dāng)前進程的硬件上下文數(shù)據(jù),即cpu內(nèi)寄存器的內(nèi)容。
保存在哪里? 保存到進程的task_struct里面如何區(qū)分新的進程和已經(jīng)調(diào)度過的進程? 在task_struct中增加一個標(biāo)記位。5.Linux2.6內(nèi)核進程O(1)調(diào)度隊列

一個CPU擁有一個runqueue如果有多個CPU就要考慮進程個數(shù)的負載均衡問題優(yōu)先級普通優(yōu)先級:100?139(我們都是普通的優(yōu)先級,想想nice值的取值范圍,可與之對應(yīng)!)實時優(yōu)先級:0?99(不關(guān)心)活動隊列時間片還沒有結(jié)束的所有進程都按照優(yōu)先級放在該隊列nr_active:總共有多少個運行狀態(tài)的進程queue[140]:一個元素就是一個進程隊列,相同優(yōu)先級的進程按照FIFO規(guī)則進行排隊調(diào)度,所以,數(shù)組下標(biāo)就是優(yōu)先級!從該結(jié)構(gòu)中,選擇一個最合適的進程,過程是怎么的呢? a. 從0下標(biāo)開始遍歷queue[140] b. 找到第一個非空隊列,該隊列必定為優(yōu)先級最高的隊列 c. 拿到選中隊列的第一個進程,開始運行,調(diào)度完成! d. 遍歷queue[140]時間復(fù)雜度是常數(shù)!但還是太低效了!bitmap[5]:一共140個優(yōu)先級,一共140個進程隊列,為了提高查找非空隊列的效率,就可以用5*32個比特位表示隊列是否為空,這樣,便可以大 大提高查找效率。

過期隊列過期隊列和活動隊列結(jié)構(gòu)一模一樣過期隊列上放置的進程,都是時間片耗盡的進程當(dāng)活動隊列上的進程都被處理完畢之后,對過期隊列的進程進行時間片重新計算active指針和expired指針active指針永遠指向活動隊列expired指針永遠指向過期隊列活動隊列上的進程會越來越少,過期隊列上的進程會越來越多,因為進程時間片到期時一直都存在的。在合適的時候,只要能夠交換active指針和expired指針的內(nèi)容,就相當(dāng)于有具有了一批新的活動進程!
linux真是算法調(diào)度:O(1)調(diào)度算法 再次理解nice值:nice值是為了保證老進程的優(yōu)先級不被強制改變,原本進程的優(yōu)先級不改變,加上一個nice值,當(dāng)本次調(diào)度完重新放入過期隊列時,更新優(yōu)先級,鏈入到指定位置。