前言:歡迎各位光臨本博客,這里小編帶你直接手撕Make/Makefile (自動化構建),文章并不復雜,愿諸君耐其心性,忘卻雜塵,道有所長!!!!
**🔥個人主頁:IF’Maxue-CSDN博客
🎬作者簡介:C++研發方向學習者
📖**個人專欄:
《C語言》
《C++深度學習》
《Linux》
《數據結構》
《數學建模》**??人生格言:生活是默默的堅持,毅力是永久的享受。不破不立,遠方請直行!
文章目錄
- 一、先搞懂:進程狀態藏在哪?
- 二、課本里的核心邏輯:3句話說透
- 三、基礎三態:運行、阻塞、掛起(大白話版)
- 1. 運行態:“在工位上,要么干活要么等活”
- 2. 阻塞態:“等材料,暫時離開工位”
- 3. 掛起態:“工位不夠,去臨時倉庫待著”
- 四、內核小知識:用“鏈表”管進程
- 五、Linux實際進程狀態:逐個拆,附代碼
- 1. R狀態:運行態(Running)
- 2. S狀態:可中斷休眠(淺睡眠)
- 3. T狀態:暫停態(Stopped)
- 4. t狀態:斷點態(Trace Stopped)
- 5. D狀態:不可中斷休眠(深睡眠)
- 6. Z狀態:僵尸態(Zombie)
- 7. X狀態:死亡態(Dead)
- 六、孤兒進程:爹跑了,誰管孩子?
- 為啥要領養?
- 七、總結:3個核心記牢
一、先搞懂:進程狀態藏在哪?
進程不是“黑盒子”,它的所有信息——比如在干啥、代碼存在哪、有啥權限——都記在一個叫task_struct
的“檔案本”里。而進程狀態,就是這個檔案本里的一個整數,像員工的“工作狀態標簽”,直接告訴操作系統“這進程現在能干活不”。
二、課本里的核心邏輯:3句話說透
不管啥操作系統,進程狀態的底層邏輯都逃不開這3點(對應你放的課本截圖):
- 進程不只有“跑”和“不跑”,有好幾種狀態;
- 狀態能互相轉——比如“等鍵盤輸入”時會從“能跑”變“等著”,輸入完又變回去;
- 只有“運行狀態”的進程,才真正拿著CPU干活。
三、基礎三態:運行、阻塞、掛起(大白話版)
咱們用“工廠干活”的例子理解,比硬記定義簡單:
1. 運行態:“在工位上,要么干活要么等活”
操作系統里,每個CPU都有一個“調度隊列”(像工廠的“待崗工位”),里面放的全是task_struct
(進程檔案本)。
只要進程在這個隊列里,狀態就是運行態(Running) ——不管是正在被CPU“叫去干活”,還是排隊等CPU,都算運行態。
調度時,CPU會按規則(比如“先進先出”FIFO)從隊列里挑一個檔案本,找到對應的代碼和數據,讓它跑起來。
2. 阻塞態:“等材料,暫時離開工位”
比如你寫了個帶scanf
的程序——運行后不敲鍵盤,進程就“卡住了”。這不是它偷懶,是它在等“鍵盤輸入”這個“材料”,這就是阻塞。
操作系統怎么管這種情況?它會給每個硬件(鍵盤、磁盤、網卡)建個“設備檔案”(struct device
),檔案里專門留了個“等待隊列”——所有等這個硬件的進程,都會被移到這個隊列里。
簡單說:阻塞 = 進程檔案本從“調度隊列”挪到“硬件等待隊列”,直到“材料”備好(比如你敲了鍵盤),再挪回調度隊列。
3. 掛起態:“工位不夠,去臨時倉庫待著”
如果內存實在不夠(工廠工位滿了),操作系統會把進程的“代碼和數據”挪到磁盤(臨時倉庫),只留task_struct
(檔案本)在內存——這叫“阻塞掛起”(本來就在等材料,現在連工具都收起來了)。
要是內存還不夠,連調度隊列里的進程也會被挪去磁盤,叫“運行掛起”(本來在等活,現在先去倉庫)。
不過不用記這么細——Linux里不細分掛起態,咱們重點看實際能用的狀態就行。
四、內核小知識:用“鏈表”管進程
操作系統不是瞎管進程的,靠的是“鏈表”這種數據結構。Linux用的是list_head
鏈表,特點很實用:
- 每個
task_struct
里可以放多個list_head
(像一個員工有多個“身份標簽”); - 這樣一個進程能同時屬于多個鏈表——比如既在“進程組鏈表”,又在“調度隊列鏈表”;
- 想通過
list_head
找到完整的task_struct
?靠“地址偏移”——知道list_head
在檔案本里的位置,就能算出整個檔案本的地址。
簡單說:進程狀態切換,本質就是list_head
在不同鏈表間“挪位置”,無非是增刪查改的操作。
五、Linux實際進程狀態:逐個拆,附代碼
Linux的進程狀態存在task_state_array
里,咱們逐個講,每個都給代碼例子,跟著做就能看懂。
1. R狀態:運行態(Running)
- 特點:在調度隊列里,要么正在跑,要么等CPU;不能被kill打斷(想停它得等它自己出隊列)。
- 為啥有時查不到?比如
printf
這種操作太快,進程瞬間切到其他狀態,得用“死循環不做IO”才能穩定抓到。
代碼例子(抓R狀態):
#include <stdio.h>
int main() {while(1); // 死循環,不做任何IO,一直待在調度隊列return 0;
}
- 操作步驟:
- 編譯:
gcc test.c -o test
; - 后臺運行(不占終端):
./test &
; - 查看狀態:
ps aux | grep test
,會看到狀態是R
。
- 編譯:
(注:狀態后的+
表示“前臺進程”,后臺進程沒有+
)
2. S狀態:可中斷休眠(淺睡眠)
- 特點:等資源(鍵盤、文件),處于“淺睡”;能被kill命令打斷(比如等輸入時,kill一下就退出)。
- 最常見的阻塞態,比如
scanf
等輸入、讀文件時都算S狀態。
代碼例子(抓S狀態):
#include <stdio.h>
int main() {int a;scanf("%d", &a); // 等鍵盤輸入,進程阻塞,狀態變Sprintf("%d\n", a);return 0;
}
- 操作步驟:
- 運行:
./test
,不輸入任何內容; - 另開終端查狀態:
ps aux | grep test
,狀態是S
; - 測試kill:
kill 進程號
,進程會直接退出(S狀態能被打斷)。
- 運行:
3. T狀態:暫停態(Stopped)
- 特點:進程被“暫停”,既不在調度隊列也不在等待隊列;得用命令恢復(
kill -18
)。 - 觸發方式:按
ctrl+z
暫停前臺進程,或用kill -19
手動暫停。
操作例子:
- 運行上面的
scanf
程序:./test
; - 按
ctrl+z
,終端提示“已暫停”; - 查狀態:
ps aux | grep test
,狀態是T
; - 恢復:
kill -18 進程號
,進程繼續等輸入; - 再暫停:
kill -19 進程號
,變回T。
4. t狀態:斷點態(Trace Stopped)
- 特點:只有調試時會出現!進程在斷點處停下,比如gdb設斷點后運行。
操作例子:
- 用gdb調試:
gdb ./test
; - 設斷點:
b main
(在main函數開頭停); - 運行:
r
; - 另開終端查狀態:
ps aux | grep test
,狀態是t
。
5. D狀態:不可中斷休眠(深睡眠)
- 特點:等“關鍵資源”(比如磁盤IO),處于“深睡”;kill -9都殺不掉(怕打斷磁盤操作導致數據損壞)。
- 常見場景:用
dd
命令拷貝大文件時。
操作例子(抓D狀態):
- 執行磁盤讀寫命令:
dd if=/dev/zero of=/tmp/test bs=1G count=10
(往/tmp寫10G文件); - 另開終端查狀態:
ps aux | grep dd
,狀態是D
; - 試殺:
kill -9 進程號
,進程紋絲不動,直到磁盤操作完成才退出。
6. Z狀態:僵尸態(Zombie)
- 特點:子進程退出了,但父進程沒“要它的退出信息”(比如退出碼),只剩
task_struct
(檔案本)在內存;不能被調度,也殺不掉(因為進程已經死了,只剩空殼)。 - 風險:僵尸進程占內存,父進程一直不管就會“內存泄露”(尤其是開機就跑的常駐進程)。
代碼例子(造僵尸進程):
#include <stdio.h>
#include <unistd.h>
int main() {pid_t pid = fork(); // 創建子進程if (pid == 0) {// 子進程:直接退出,沒被回收printf("子進程PID:%d\n", getpid());return 0;} else if (pid > 0) {// 父進程:死循環,不回收子進程while(1) sleep(1); }return 0;
}
- 操作步驟:
- 運行:
./test
; - 查狀態:
ps aux | grep test
,會看到子進程狀態是Z
; - 解決辦法:讓父進程調用
wait()
回收,或殺掉父進程(子進程會被領養)。
- 運行:
(注:slab
是Linux內核的內存分配機制,專門管理像task_struct
這樣的小對象,僵尸進程的task_struct
就存在這里,不回收會占 slab 內存)
7. X狀態:死亡態(Dead)
- 特點:進程徹底退出,所有資源(代碼、數據、
task_struct
)被OS回收;看不到這個狀態(因為瞬間就沒了),只是理論上的狀態。
六、孤兒進程:爹跑了,誰管孩子?
如果父進程先退出,子進程沒人管,就成了“孤兒進程”——這時候Linux會讓1號進程領養它:
- 新內核(比如Ubuntu、CentOS 7+):1號進程是
systemd
; - 老內核:1號進程是
init
。
為啥要領養?
怕孤兒進程退出后沒人回收,變成僵尸進程,導致內存泄露。1號進程會負責“要它的退出信息”,相當于“福利院”。
代碼例子(造孤兒進程):
#include <stdio.h>
#include <unistd.h>
int main() {pid_t pid = fork();if (pid == 0) {// 子進程:睡10秒,等父進程先退出printf("子進程PID:%d,當前父進程PID:%d\n", getpid(), getppid());sleep(10); printf("子進程現在父進程PID:%d\n", getppid()); // 會變成1return 0;} else if (pid > 0) {// 父進程:馬上退出,不管子進程printf("父進程退出,PID:%d\n", getpid());return 0;}return 0;
}
- 操作步驟:
- 運行:
./test
; - 父進程瞬間退出,子進程一開始的父進程是終端(比如
bash
); - 10秒后,子進程的父進程變成1(被1號進程領養);
- 孤兒進程會變成后臺進程,終端看不到它的輸出(除非重定向)。
- 運行:
七、總結:3個核心記牢
- 進程狀態本質:
task_struct
在不同鏈表間挪位置(調度隊列、等待隊列); - Linux重點狀態:R(跑)、S(淺睡等資源)、D(深睡殺不掉)、Z(僵尸要回收)、T(暫停);
- 孤兒/僵尸處理:孤兒找1號進程領養,僵尸靠父進程
wait()
回收,避免內存泄露。