1. 前文鋪墊
進程狀態是task_struct內的一個整數;進行:進程在調度隊列中,進程的狀態都是running,阻塞:等待某種設備或者資源就緒。進程是一個隊列,設備也是一個隊列,當我們讀磁盤,讀網卡的時候,如果對應設備未就緒那么進程就要阻塞等待了。進程狀態變化的表現之一就是要在不同的隊列中進行流動,本質都是數據結構的增刪查改!
理解內核鏈表
在這里插入圖片描述
如果一個類里面有多個next,prve,那么就可以把任何一個task_struct即屬于運行隊列,又屬于全局鏈表,還可以把它放到二叉樹中等。
在這里插入圖片描述
2. 進程狀態
一個進程可以有幾個狀態(在Linux內核里,進程有時候也叫做任務)。
下面的狀態在kernel源代碼里定義:
/* *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. */ staticconstchar*consttask_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)
狀態描述:進程正在CPU上執行,或在運行隊列中等待調度。
觸發場景:進程處于活動狀態,正在執行或準備執行。
S 可中斷睡眠(Interruptible Sleep)
狀態描述:進程在等待事件完成(如I/O操作、信號),可被信號中斷。
觸發場景:例如調用sleep()、read()等阻塞操作時。
D 不可中斷睡眠(Uninterruptible Sleep)
狀態描述:進程等待不可中斷的操作(如硬件I/O),不響應信號。
觸發場景:常見于磁盤I/O或某些內核操作,需等待操作完成。
T 停止(Stopped)
狀態描述:進程被信號(如SIGSTOP、SIGTSTP)暫停,需SIGCONT恢復。
觸發場景:手動暫停進程(如按Ctrl+Z)或調試時。
Z 僵尸(Zombie)
狀態描述:進程已終止,但父進程未調用wait()回收資源。
觸發場景:父進程未正確處理子進程退出,導致殘留進程描述符。
t 追蹤狀態(Tracing Stop)
狀態描述:進程被調試器(如gdb)跟蹤時暫停,屬于停止狀態的一種。
觸發場景:調試器設置斷點或單步執行時。
X 死亡(Dead)
狀態描述: 子進程結束之后,父進程獲取子進程信息之前。
觸發場景: 父進程已回收子進程狀態,短暫存在后消失。
2.1 進程狀態查看
命令:ps aux / ps axj
a:用于顯示所有用戶的進程,包括其他用戶的進程(需要適當的權限)。默認情況下,ps 只顯示當前用戶的進程。
例如:ps a
x:用于顯示沒有控制終端的進程。這些進程通常是后臺運行的守護進程(daemon)。
例如:ps x
通常,ax選項一起使用,以顯示所有用戶的所有進程,無論它們是否有控制終端。
例如:ps ax
j:用于顯示與作業控制相關的信息,包括進程組ID(PGID)、會話ID(SID)、父進程ID(PPID),以及作業號(如果有的話)。
例如:ps j
這對于理解進程如何分組和作業控制很有幫助。
u :用于以用戶為中心的格式顯示進程信息。它提供了每個進程的詳細信息,如用戶、CPU使用率、內存使用率、虛擬內存大小、駐留內存大小、控制終端、進程狀態、啟動時間、CPU時間和命令行。
例如:ps u
通常,u 選項與 aux 一起使用,以顯示所有用戶的詳細進程信息。
例如:ps aux
2.2 僵尸進程
僵死狀態(Zombies)是一個比較特殊的狀態。當進程退出并且父進程(使用wait()系統調用)沒有讀取到子進程退出的返回代碼時就會產生僵死(尸)進程。
僵死進程會以終止狀態保持在進程表中,并且會一直在等待父進程讀取退出狀態代碼。
只要子進程退出,父進程還在運行,但父進程沒有讀取子進程狀態,子進程進入Z狀態。
2.3 僵尸進程危害
進程的退出狀態必須被維持下去,因為他要告訴關心它的進程(父進程),你交給我的任務,我
辦的怎么樣了。可父進程如果一直不讀取,那子進程就一直處于Z狀態?是的!
維護退出狀態本身就是要用數據維護,也屬于進程基本信息,所以保存在task_struct(PCB)中,換句話說,Z狀態?直不退出,PCB一直都要維護?是的!
那一個父進程創建了很多子進程,就是不回收,是不是就會造成內存資源的浪費?是的!因為數據結構對象本身就要占用內存,C語言中定義一個結構體變量(對象),是要在內存的某個位置進行開辟空間,那就會存在內存泄漏?是的!
如何避免呢?我們后期講。
2.4 孤兒進程
我們先來創建一段代碼
在這里插入圖片描述
代碼運行后,子進程一直運行,父進程運行5秒后退出
在這里插入圖片描述
這個1號進程是誰呢?top一下,我們可以看到它是systemd
在這里插入圖片描述
我們繼續查一下這個systemd
在這里插入圖片描述
為什么子進程會被1(systemd)號進程領養呢?如果不領養會出現什么問題呢?
答案是如果不被領養,那么這個子進程就進入僵尸進程,有可能會造成內存泄漏
父進程為什么不會變成孤兒進程或者僵尸進程呢?
答案是父進程也有自己的父進程,父進程的父進程就是bash
一旦進程變成孤兒進程,它就會被1號進程領養,變成后臺進程,這時候Ctrl c就殺不掉它了,我們只能使用kill來殺死。
3. 進程優先級
3.1 概念
cpu資源分配的先后順序,就是指進程的優先級(priority)。
目標資源稀缺,導致要通過優先級確認誰先誰后的問題。
優先權高的進程有優先執行權利。配置進程優先級對多任務環境的linux很有用,可以改善系統性能。
還可以把進程運行到指定的CPU上,這樣一來,把不重要的進程安排到某個CPU,可以大大改善系統整體性能。
優先級 vs 權限:優先級是能得到資源,先后的問題,權限是能否得到資源的問題
優先級其實也是一種數字,是task_struct中的一種屬性,數字值越低,優先級越高 ,基于時間片的分時操作系統,優先級未來可能變化,但變化的幅度不能太大
3.2 查看系統進程
命令ps -al,其中a表示所有,l表示詳細信息。
我們上上面代碼中的父進程不再退出,父子進程一直運行,再運行代碼
在這里插入圖片描述
在linux系統中,每個用戶都有一個UID,linux中識別用戶就是用UID識別的。
UID :代表執行者的身份
PID :代表這個進程的代號
PPID :代表這個進程是由哪個進程發展衍生出來的,亦即父進程的代號
PRI :代表這個進程可被執行的優先級,其值越小越早被執行。進程優先級默認:80
NI :代表這個進程優先級的修正數據,nice值
進程真實的優先級 = PRI(默認) + NI
PRI(new) = PRI(old) + nice
所以,調整進程優先級,在Linux下,就是調整進程nice值
nice 其取值范圍是-20至19,一共40個級別。linux進程優先級范圍[60,99]
查UID,命令ls -ln
在這里插入圖片描述
sp用戶對應的UID就是1001,文件創建的時候會把這個UID保存起來表明這個文件是誰創建的,進程創建的時候也會把UID保存起來表明進程是誰創建的。
所以當我們訪問一個文件時,系統怎么識別出我們是擁有者,所屬組,或者other呢,我們訪問文件時本質就是進程在訪問文件,進程怎么知道我們是誰呢?答案是是誰啟動的這個進程,進程就知道這個人的UID,這個文件是誰創建的這個文件的UID就有了,所以一個進程將來拿著它的UID和文件的UID做對比,相等了就是擁有者,不相等查下一個,兩個都不相等就是other。
linux系統中,訪問任何資源都是進程訪問,進程就代表用戶。
3.3 查看進程優先級的命令
用top命令更改已存在進程的nice:
top
進入top后按“r”?>輸入進程PID?>輸入nice值
其他調整優先級的命令:nice,renice
linux調整優先級的系統調用
3.4 補充概念-競爭、獨立、并行、并發
競爭性: 系統進程數目眾多,而CPU資源只有少量,甚至1個,所以進程之間是具有競爭屬性的。為了高效完成任務,更合理競爭相關資源,便具有了優先級
獨立性: 多進程運行,需要獨享各種資源,多進程運行期間互不干擾
并行: 多個進程在多個CPU下分別,同時進行運行,這稱之為并行
并發: 多個進程在?個CPU下采用進程切換的方式,在一段時間之內,讓多個進程都得以推進,稱之為并發
在這里插入圖片描述
4. 進程切換
先談兩個問題:
死循環進程是如何運行的
我們平時在vs中寫一個while(1)的死循環,一旦跑起來我們就會發現系統會變卡了,但是不會卡死。
a.一旦一個進程占有CPU,會把自己的代碼跑完嗎?不會!(除非這個代碼很短)每個進程系統都會為它分配一個叫做時間片的東西。所以每一個進程擁有CPU資源都不是永久性的,而是臨時性的。
b.死循環進程不會打死進程,因為死循環進程不會一直占用CPU!
cpu,寄存器
cpu執行一個進程的時候就和PCB的關系不大了,cpu重點是訪問的是進程的代碼和數據,所以cpu會訪問當前進程的代碼和數據,為了能夠處理一條一條的代碼和數據,所以cpu中會存在很多的寄存器,每個寄存器在cpu內部都有著臨時保存數據的任務,所以當進程再跑時,寄存器就會被填上臨時值,有的是計算結果,浮點數計算有沒有錯誤等。
結論:a.寄存器就是cpu內部的臨時空間 b.寄存器 != 寄存器里面的數據
進程如何切換?
CPU上下文切換:其實際含義是任務切換,或者CPU寄存器切換。當多任務內核決定運行另外的任務時,它保存正在運行任務的當前狀態,也就是CPU寄存器中的全部內容。這些內容被保存在任務自己的堆棧中,入棧工作完成后就把下一個將要運行的任務的當前狀況從該任務的棧中重新裝入CPU寄存器,并開始下一個任務的運行,這一過程就是context switch。
在這里插入圖片描述
進程切換最核心的就是保存和恢復當前進程的硬件上下文數據,即cpu內寄存器的內容。
保存在哪里?
保存到進程的task_struct里面
如何區分新的進程和已經調度過的進程?
在task_struct中增加一個標記位。
5.Linux2.6內核進程O(1)調度隊列
在這里插入圖片描述
一個CPU擁有一個runqueue
如果有多個CPU就要考慮進程個數的負載均衡問題
優先級
普通優先級:100?139(我們都是普通的優先級,想想nice值的取值范圍,可與之對應!)
實時優先級:0?99(不關心)
活動隊列
時間片還沒有結束的所有進程都按照優先級放在該隊列
nr_active :總共有多少個運行狀態的進程
queue[140] :一個元素就是一個進程隊列,相同優先級的進程按照FIFO規則進行排隊調度,所以,數組下標就是優先級!
從該結構中,選擇一個最合適的進程,過程是怎么的呢?
a. 從0下標開始遍歷queue[140]
b. 找到第一個非空隊列,該隊列必定為優先級最高的隊列
c. 拿到選中隊列的第一個進程,開始運行,調度完成!
d. 遍歷queue[140]時間復雜度是常數!但還是太低效了!
bitmap[5] :一共140個優先級,一共140個進程隊列,為了提高查找非空隊列的效率,就可以用5*32個比特位表示隊列是否為空,這樣,便可以大 大提高查找效率。
在這里插入圖片描述
過期隊列
過期隊列和活動隊列結構一模一樣
過期隊列上放置的進程,都是時間片耗盡的進程
當活動隊列上的進程都被處理完畢之后,對過期隊列的進程進行時間片重新計算
active指針和expired指針
active 指針永遠指向活動隊列
expired 指針永遠指向過期隊列
活動隊列上的進程會越來越少,過期隊列上的進程會越來越多,因為進程時間片到期時一直都存在的。
在合適的時候,只要能夠交換active指針和expired指針的內容,就相當于有具有了一批新的活動進程!
linux真是算法調度:O(1)調度算法
再次理解nice值:nice值是為了保證老進程的優先級不被強制改變,原本進程的優先級不改變,加上一個nice值,當本次調度完重新放入過期隊列時,更新優先級,鏈入到指定位置。
-
內核
+關注
關注
3文章
1415瀏覽量
41252 -
Linux
+關注
關注
87文章
11496瀏覽量
213253 -
指針
+關注
關注
1文章
484瀏覽量
71105
原文標題:Linux進程狀態(僵尸進程,孤兒進程),優先級與調度機制
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
Linux系統下進程的幾種狀態介紹

Linux設備驅動開發詳解
【Linux學習雜談】之進程狀態
詳解如何監控和保護Linux下進程安全
你知道Linux進程的睡眠和喚醒操作?

評論