為了理解正在運行的進程的含義,我們需要了解進程的不同狀態。進程在linux內核中也被稱為任務。進程的狀態由task_struct中的一個整型變量表示。以下是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. */ 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 */ };
為什么需要進程狀態?在日常生活中,如果你感冒了,你可能會告訴室友今天狀態不好不去上課了。這里的狀態決定了你后續的行動——不去上課。在linux中,操作系統需要根據進程的狀態來決定對這些進程的后續操作。
1.1 通俗的五種進程狀態
進程的狀態可以通俗地分為五種:創建狀態、就緒狀態、阻塞狀態、執行狀態和終止狀態。最基本的狀態包括:運行狀態、就緒狀態和阻塞狀態。
-
就緒狀態:進程已經具備運行條件,但由于沒有空閑的CPU而暫時不能運行。
-
運行狀態:進程正在運行,占用CPU資源。
-
阻塞狀態:進程因為等待某一事件而暫時不能運行,如執行sleep或等待輸入。
-
創建狀態:進程正在被創建,操作系統為其分配資源、初始化PCB。
-
終止狀態:進程從系統中被撤銷,操作系統回收進程擁有的資源。
1.2 進程的具體狀態
前面提到的狀態與我們之前描述的具體狀態有所不同,具體狀態是通俗狀態的具體實例。讓我們再看看代碼:
/* * 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):并不意味著進程一定在運行,它表示進程要么在運行中,要么在運行隊列中,相當于就緒狀態和運行狀態。
- S 睡眠狀態(sleeping):進程在等待事件完成(也稱為可中斷睡眠),相當于阻塞狀態。
- D 磁盤休眠狀態(disk sleep):有時也稱為不可中斷睡眠狀態,進程通常等待I/O操作結束。
- T 停止狀態(stopped):可以通過發送SigsTOP信號給進程來停止進程,暫停的進程可以通過SIGCONT信號繼續運行。
- X 死亡狀態(dead):這是一個返回狀態,不會在任務列表中顯示。
- Z 僵尸狀態(zombie):當進程退出且父進程未讀取其返回代碼時,進程進入僵尸狀態。
1.3 查看進程狀態
我們可以通過以下命令查看進程狀態:
ps aux / ps axj 命令
- ps aux:顯示所有進程,以用戶為主。
- ps axj:顯示所有進程,并顯示進程ID、父進程ID等信息。
讓我們編寫一個程序并觀察其狀態:
#include <stdio.h> #include <unistd.h> int main(){ while(1){ printf("I am a process!n"); sleep(1); //休眠一秒 } return 0; }
Makefile配置如下:
mybin:test1.c gcc -o mybin test1.c .PHONY:clean clean: rm -f mybin
使用兩個xshell窗口,一個運行程序,另一個觀察進程狀態。我們還可以編寫一個循環命令來查看進程狀態:
while :;do ps ajx|head -1 && ps ajx|grep mybin|grep -v grep;sleep 1; done
每秒打印一次mybin進程的狀態。運行程序后,我們可以看到進程的STAT狀態為S,這是因為程序中的sleep函數會讓進程休眠一秒。如果去掉sleep,STAT仍然會是S,因為printf也會導致短暫的休眠。要顯示R狀態,可以去掉printf,讓程序執行死循環。
1.4 介紹僵尸進程與孤兒進程
-
Z(zombie) – 僵尸進程:當進程退出且父進程未讀取其返回代碼時,進程進入僵尸狀態。僵尸進程會一直保持在進程表中,等待父進程讀取其退出狀態。僵尸進程會導致內存泄漏。
我們可以創建一個維持30秒的僵尸進程:
#include <stdio.h> #include <stdlib.h> int main(){ pid_t id = fork(); if(id > 0){ printf("parent[%d] is sleeping...n",getpid()); sleep(30); }else{ printf("child[%d] is begin Z...n",getpid()); sleep(5); exit(-1); } return 0; }
-
孤兒進程:如果父進程提前退出,子進程后退出并進入Z狀態,子進程會成為孤兒進程,由1號進程收養。
我們可以編寫一個程序來觀察孤兒進程:
#include <stdio.h> #include <stdlib.h> int main(){ pid_t id = fork(); if(id > 0){ printf("parent[%d] is sleeping...n",getpid()); sleep(3); exit(-1); }else{ printf("child[%d] is begin Z...n",getpid()); sleep(10); } return 0; }
通過這些示例,我們可以看到子進程被1號進程接管。