前言
????????在Linux系統中,進程狀態是系統管理和性能調優的核心知識。一個進程從誕生到終止,會經歷運行(R)、可中斷睡眠(S)、不可中斷睡眠(D)、停止(T)、僵尸(Z)等多種狀態。理解這些狀態的含義、觸發條件及觀察方法,是診斷進程掛起、資源泄漏等問題的關鍵。
目錄
一、進程狀態觀察詳解
1、運行狀態(R - Running/TASK_RUNNING)
示例觀察方法
1. 創建測試程序
2. 運行程序并觀察狀態
方法一:使用top命令
方法二:使用ps命令
2、可中斷睡眠(S - Interruptible Sleep/TASK_INTERRUPTIBLE)
3、不可中斷睡眠(D - Uninterruptible Sleep/TASK_UNINTERRUPTIBLE)
方法一:模擬磁盤I/O導致的D狀態
1. 創建測試腳本
2. 準備觀察環境
第一步:查看可用設備
第二步:創建掛載點目錄
第三步:執行掛載一個慢速存儲設備
第四步:驗證掛載
第五步:給腳本執行權限:
第六步:運行并觀察
4、停止狀態(T - Stopped/TASK_STOPPED)
5、跟蹤狀態(T - Tracing Stop)
1. 創建進程并進入跟蹤狀態
2. 使用調試器附加到進程
3. 觀察進程狀態
4. 狀態變化示例
5. 其他產生T狀態的場景(了解,不要求掌握)
6. 如何解除T狀態
6、僵尸狀態(Z - Zombie/EXIT_ZOMBIE)?
1. 創建僵尸進程的C程序
2. 編譯并運行程序
3. 觀察進程狀態
4. 預期觀察到的現象
5. 清理僵尸進程(后面會講解,現在先了解)
6. 僵尸進程的特點
7. 僵尸進程的危害
7、死亡狀態(X - Dead/EXIT_DEAD)
理解死亡狀態的特點
二、常見問題
三、總結
一、進程狀態觀察詳解
1、運行狀態(R - Running/TASK_RUNNING)
????????進程處于運行狀態(running)并不等同于正在執行。運行狀態意味著該進程要么正在CPU上運行,要么處于運行隊列中等待執行。因此,系統中可以同時存在多個R狀態的進程。
重點:
- 所有可調度的運行狀態進程都會被放入運行隊列
- 操作系統進行進程切換時,直接從運行隊列中選擇下一個要執行的進程
- 單核CPU上同一時刻只有一個進程真正在CPU上運行
-
其他R狀態的進程都在運行隊列中等待
-
多核CPU上可以有多個進程同時處于"正在運行"狀態
示例觀察方法
1. 創建測試程序
創建一個簡單的CPU密集型程序來觀察R狀態:
// cpu_busy.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int main() {printf("PID: %d\n", getpid());while(1) {// 空循環保持CPU占用}return 0;
}
編譯:
gcc cpu_busy.c -o cpu_busy
2. 運行程序并觀察狀態
方法一:使用top命令
-
在當前終端窗口運行程序:
./cpu_busy
-
在另一個終端窗口運行top:
top
在top界面中,按Shift + P
按CPU使用排序、觀察STAT
列,會顯示R
狀態:
方法二:使用ps命令
ps aux | grep cpu_busy
輸出示例:
其中R+
表示:
-
R
:運行狀態 -
+
:在前臺進程組中
2、可中斷睡眠(S - Interruptible Sleep/TASK_INTERRUPTIBLE)
????????一個進程處于淺度睡眠狀態(sleeping)表示它正在等待某個事件的完成。處于這種狀態的進程可以被隨時喚醒或終止(這種狀態也被稱為可中斷睡眠,即interruptible sleep)。
例如運行以下代碼:
#include <stdio.h>
#include <unistd.h>int main()
{printf("I am running... \n");sleep(100);return 0;
}
編譯并運行:?
????????在代碼中調用sleep函數休眠100秒期間,若用其他用戶查看進程狀態,會發現進程處于淺度睡眠狀態:
處于淺度睡眠狀態的進程可以被終止,使用kill命令即可強制結束該進程:
3、不可中斷睡眠(D - Uninterruptible Sleep/TASK_UNINTERRUPTIBLE)
????????當進程處于深度睡眠狀態(Disk Sleep)時,系統無法終止該進程,只能等待其自行喚醒。這種狀態也被稱為不可中斷睡眠狀態(Uninterruptible Sleep),通常發生在進程等待I/O操作完成時。
????????以磁盤寫入為例:當進程發起寫盤請求后,會進入深度睡眠狀態等待磁盤響應(如寫入成功/失敗的通知)。在此狀態下,即使系統也無法終止該進程,必須等待I/O操作完成。(又稱磁盤休眠狀態)
方法一:模擬磁盤I/O導致的D狀態
1. 創建測試腳本
創建一個會觸發磁盤I/O的腳本:
#!/bin/bash
# 保存為 disk_io_test.shecho "PID: $$"
echo "將模擬磁盤I/O操作..."# 向慢速設備(如U盤/NFS)寫入大量數據
dd if=/dev/zero of=/mnt/nfs/testfile bs=1M count=1024 2>/dev/nullecho "I/O操作完成"
2. 準備觀察環境
第一步:查看可用設備
lsblk # 查看所有存儲設備
輸出示例:
????????根據lsblk
?的輸出,可以看到我的系統只有一個虛擬磁盤設備(vda),沒有檢測到其他存儲設備。這是典型云服務器或虛擬機的配置。
-
vda:虛擬磁盤(40GB)
-
vda1:第一個分區(已掛載到根目錄?
/
)
第二步:創建掛載點目錄
sudo mkdir /mnt/mydisk # 創建一個新目錄作為掛載點
第三步:執行掛載一個慢速存儲設備
sudo mount /dev/vda1 /mnt/mydisk
第四步:驗證掛載
df -h # 查看已掛載的文件系統
或
ls /mnt/mydisk # 查看掛載的設備內容
第五步:給腳本執行權限:
chmod +x disk_io_test.sh
第六步:運行并觀察
-
在一個終端運行腳本:
./disk_io_test.sh
-
在另一個終端觀察進程狀態:
watch -n 0.5 'ps aux | grep disk_io_test | grep -v grep'
????????命令剛啟動時的狀態,可能還未進入深度睡眠(D狀態),而如果進入深度睡眠(D狀態),會輸出示例,必須要有產生足夠的 I/O 壓力,才會實現(我模擬不了,大概看一下就行):
user 12345 0.0 0.0 12345 678 pts/1 D+ 14:30 0:05 /bin/bash ./disk_io_test.sh
其中D+
表示:
-
D
:深度睡眠狀態(不可中斷) -
+
:前臺進程組
4、停止狀態(T - Stopped/TASK_STOPPED)
????????Linux系統可通過發送SIGSTOP信號將進程掛起為暫停狀態(Stopped),發送SIGCONT信號則可恢復被暫停的進程運行。
例如:
向某進程發送SIGSTOP信號后,該進程立即進入暫停狀態:
直到收到SIGCONT信號才會繼續執行:
5、跟蹤狀態(T - Tracing Stop)
1. 創建進程并進入跟蹤狀態
首先,我們需要創建一個可以被跟蹤的進程:
#include <stdio.h>
#include <unistd.h>int main() {printf("子進程PID: %d\n", getpid());while(1) {// 無限循環保持進程運行sleep(1);}return 0;
}
編譯并運行這個程序,記下它的PID:
2. 使用調試器附加到進程
在另一個終端中,使用gdb附加到該進程:
sudo gdb -p <PID>
使用第三個終端觀察,此時,被調試的進程會進入T (Tracing Stop)狀態:
3. 觀察進程狀態
在第三個終端中,查看進程狀態:
ps -o pid,state,cmd -p <PID>
輸出可能類似于:
4. 狀態變化示例
-
初始狀態:進程正常運行,狀態為"S"(睡眠)
-
附加調試器后:狀態變為"T"(被跟蹤)
-
繼續執行:在gdb中輸入"continue",狀態可能變回"S"
-
斷點命中:當遇到斷點時,狀態再次變為"T"
5. 其他產生T狀態的場景(了解,不要求掌握)
-
使用strace跟蹤系統調用:
strace -p <PID>
-
使用ptrace系統調用:Ptrace 詳解 - tangr206 - 博客園
// 跟蹤進程的示例代碼 ptrace(PTRACE_ATTACH, pid, NULL, NULL);
-
接收到SIGSTOP信號:
kill -SIGSTOP <PID>
6. 如何解除T狀態
-
在gdb中:使用
detach
命令或退出gdb -
對于strace:按Ctrl+C終止strace
-
對于SIGSTOP:發送SIGCONT信號
kill -SIGCONT <PID>
6、僵尸狀態(Z - Zombie/EXIT_ZOMBIE)?
1. 創建僵尸進程的C程序
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main() {pid_t pid = fork();if (pid == 0) {int cnt=60;while(cnt--){// 子進程printf("子進程 PID: %d 開始運行\n", getpid());sleep(2); // 模擬子進程工作printf("子進程 PID: %d 即將退出\n", getpid());}exit(0); // 子進程退出} else if (pid > 0) {// 父進程printf("父進程 PID: %d 創建了子進程 %d\n", getpid(), pid);printf("父進程將不調用wait(),故意制造僵尸進程\n");// 父進程不調用wait(),繼續運行while(1) {sleep(1);printf("父進程仍在運行...\n");}} else {perror("fork失敗");exit(1);}return 0;
}
2. 編譯并運行程序
gcc -o zombie zombie.c
./zombie
3. 觀察進程狀態
重新運行,然后在另一個終端窗口中,使用以下命令觀察進程狀態變化:
watch -n 1 'ps -eo pid,ppid,state,cmd | grep -E "PID|zombie"'
4. 預期觀察到的現象
-
初始階段:父子進程都處于運行狀態(S)
-
子進程退出后:
-
子進程狀態變為Z(僵尸)
-
父進程仍在運行
-
-
最終狀態:
5. 清理僵尸進程(后面會講解,現在先了解)
要清理僵尸進程,可以:
-
終止父進程:
kill -9 <父進程PID>
-
或者修改程序讓父進程調用wait():
// 在父進程代碼中添加 wait(NULL); // 回收子進程
6. 僵尸進程的特點
-
出現在進程表中,占用一個PID
-
已釋放大部分資源,僅保留退出狀態等信息
-
PPID為創建它的父進程
-
命令顯示為
[進程名] <defunct>
-
只能通過終止父進程或讓父進程調用wait()來清除
7. 僵尸進程的危害
- 僵尸進程的退出狀態必須一直維持下去,因為它要告訴其父進程相應的退出信息。可是父進程一直不讀取,那么子進程也就一直處于僵尸狀態。
- 僵尸進程的退出信息被保存在task_struct(PCB)中,僵尸狀態一直不退出,那么PCB就一直需要進行維護。
- 若是一個父進程創建了很多子進程,但都不進行回收,那么就會造成資源浪費,因為數據結構對象本身就要占用內存。
- 僵尸進程申請的資源無法進行回收,那么僵尸進程越多,實際可用的資源就越少,也就是說,僵尸進程會導致內存泄漏。
7、死亡狀態(X - Dead/EXIT_DEAD)
????????死亡狀態(X)是進程的最終狀態,表示進程已經完全終止且其資源已被系統回收。由于這個狀態持續時間極短(幾乎是瞬時的),直接觀察非常困難。
理解死亡狀態的特點
-
瞬時性:X狀態是進程從退出到完全消失之間的瞬時狀態
-
不可見性:通常無法通過
ps
等工具直接觀察到 -
前驅狀態:進程通常從Z(僵尸)狀態轉為X狀態
二、常見問題
-
僵尸進程積累:
-
原因:父進程未正確處理子進程退出。
-
解決:找到父進程ID(PPID)并重啟或發送
SIGCHLD
信號。
-
-
不可中斷進程卡住:
-
可能原因:硬件故障(如磁盤壞塊)、內核Bug。
-
排查:
dmesg
查看內核日志,檢查硬件健康。
-
-
高負載下RUNNING進程過多:使用
vmstat 1
或mpstat -P ALL 1
分析CPU競爭。 -
S
和D
狀態的區別:S
可被信號中斷,D
必須等待事件完成(即使發送kill -9
無效)。 -
為什么進程長時間處于
D
狀態?
-
可能原因:硬件故障(如磁盤損壞)、內核驅動Bug。
-
解決方案:檢查
dmesg
日志,修復硬件或更新驅動。
三、總結
-
理解進程狀態是系統調優和故障排查的基礎。
-
R
/S
/D
是常見狀態,Z
需及時處理,T
常用于調試。 -
結合
ps
、top
、/proc
等工具實時監控狀態變化。
????????掌握這些狀態及其轉換機制,能有效診斷進程掛起、資源泄漏等問題。實際應用中需結合日志和性能工具(如strace
、perf
)深入分析。