第一部分 Linux進程概述
在Linux下進程被創建後可以有5種狀態。
關於Linux進程狀態不再贅述,參考下面這2個博客鏈接即可
不過有一些值得補充的說明:
-
很多操作系統教科書將正在CPU上執行的進程定義爲RUNNING狀態、而將可執行但是尚未被調度執行的進程定義爲READY狀態,這兩種狀態在linux下統一爲 TASK_RUNNING狀態。
-
進程狀態變遷
進程自創建以後,狀態可能發生一系列的變化,直到進程退出。而儘管進程狀態有好幾種,但是進程狀態的變遷卻只有兩個方向——從TASK_RUNNING狀態變爲非TASK_RUNNING狀態、或者從非TASK_RUNNING狀態變爲TASK_RUNNING狀態。
(1)也就是說,如果給一個TASK_INTERRUPTIBLE狀態的進程發送SIGKILL信號,這個進程將先被喚醒(進入TASK_RUNNING狀態),然後再響應SIGKILL信號而退出(變爲TASK_DEAD狀態),並不會從TASK_INTERRUPTIBLE狀態直接退出。進程從非TASK_RUNNING狀態變爲TASK_RUNNING狀態,是由別的進程(也可能是中斷處理程序)執行喚醒操作來實現的。執行喚醒的進程設置被喚醒進程的狀態爲TASK_RUNNING,然後將其task_struct結構加入到某個CPU的可執行隊列中。於是被喚醒的進程將有機會被調度執行。
(2)而進程從TASK_RUNNING狀態變爲非TASK_RUNNING狀態,則有兩種途徑:
a. 響應信號而進入TASK_STOPED狀態、或TASK_DEAD狀態;
b. 執行系統調用主動進入TASK_INTERRUPTIBLE狀態 (如nanosleep系統調用)、或TASK_DEAD狀態(如exit系統調用);或由於執行系統調用需要的資源得不到滿足,而進入TASK_INTERRUPTIBLE狀態或TASK_UNINTERRUPTIBLE狀態(如select系統調用)。顯然,這兩種情況都只能發生在進程正在CPU上執行的情況下。 -
留意SIGSTOP、SIGCONT、SIGKILL信號對於進程狀態的影響。另外,像pause()(等待信號的出現才被喚醒)、wait()(等待子進程退出)等函數都會使得當前進程進入可中斷睡眠狀態。
第二部分 Fork()詳解
在Linux中手動創建一個新進程的唯一方法是使用fork()函數。
fork()函數用於從已存在的進程中創建一個新進程。新進程成爲子進程,原進程成爲父進程。子進程是父進程的一個複製品,它從父進程繼承了進程的整個地址空間,包括進程上下文、代碼段、進程堆棧、內存信息、打開的文件描述符、信號控制設定、進程優先級、進程組號、當前工作目錄、根目錄、資源限制和控制終端等。fork()出來的子進程除了進程標識符pid和父進程不一樣以外,其他的都和父進程相同。下面這張圖形象的描述了進程的地址空間。
關於fork()需要注意以下幾點
- 父進程中的返回值是子進程的進程號, 而在子進程中返回0,因此可以通過返回值來判定當前進程是父進程還是子進程。當一個父進程創建多個子進程時(注意此時fork()一定要保證在父進程運行狀態下被調用),在代碼的不同調用位置可以拿到不同的子進程pid,可參見以下代碼,注意留意父進程創建多個子進程的fork()調用位置。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
int main(){
pid_t pid;
pid = fork();
if(pid < 0){
printf("Fork1 error!\n");
exit(1);
}
if(pid > 0){
printf("the father(1) is running, pid is %d, ret value is %d\n", getpid(), pid);
pid_t pid2 = fork();
if(pid2 < 0){
printf("Fork2 error!\n");
exit(1);
}
if(pid2 > 0){
printf("the father(2) is running, pid is %d, ret value is %d\n", getpid(), pid2);
exit(0);
}else if(pid2 == 0){
printf("the second son is running, pid is %d, ret value is %d\n", getpid(), pid2);
exit(0);
}
}else if(pid == 0){
printf("the first son is running, pid is %d, ret value is %d\n", getpid(), pid);
exit(0);
}
return 0;
}
運行效果
第三部分 wait()/waitpid()詳解
(1)wait()和waitpid()區別
- wait()用於使父進程阻塞等待,直到一個子進程結束或該進程接到了一個信號爲止。當該父進程沒有子進程或者是子進程結束時會立即返回。
- waitpid()的作用與wait()相似,但是不一定要等待第一個結束的子進程,它有若干選項,比wait()更加靈活,比如提供一個非阻塞等待的版本。
(2)wait()函數
函數原型 | pid_t wait(int *status); |
---|---|
所需頭文件 | #include<sys/types.h>和#include<sys/wait.h> |
成功 | 返回已結束運行的子進程的進程號 |
失敗 | 返回-1 |
參數說明
- 參數status如果不是一個空指針,則終止進程的終止狀態就存放在statloc所指向的單元;
- 參數status如果是一個空指針,則表示父進程不關心子進程的終止狀態。
(3) waitpid()函數
函數原型 | pid_t waitpid(pid_t pid, int *status, int options); |
---|---|
所需頭文件 | #include<sys/types.h>和#include<sys/wait.h> |
成功 | 返回已結束運行的子進程的進程號 |
options爲WNOHANG時 | 沒有子進程退出則立即返回0 |
失敗 | 返回-1 |
參數說明
- pid
pid>0 | 只等待進程ID等於pid的子進程,不管其它已經有多少子進程運行結束退出了,只要指定的子進程還沒有結束,waitpid就會一直等下去。 |
---|---|
pid=-1 | 等待任何一個子進程退出,沒有任何限制,此時waitpid和wait的作用一模一樣。 |
pid=0 | 等待同一個進程組中的任何子進程,如果子進程已經加入了別的進程組,waitpid不會對它做任何理睬。 |
pid<-1 | 等待一個指定進程組中的任何子進程,這個進程組的ID等於pid的絕對值。 |
- options
WNOHANG | 若由pid指定的子進程未發生狀態改變(沒有結束),則waitpid()不阻塞,立即返回0 |
---|---|
WUNTRACED | 返回終止子進程信息和因信號停止的子進程信息 |
WCONTINUED | 返回收到SIGCONT信號而恢復執行的已停止子進程狀態信息 |
0 | 這個時候跟wait()一樣阻塞等待 |
- status
同wait()
用法
- 一個常見的用法是
waitpid(pid, NULL, 0)
,這樣就可以阻塞等待進程號爲pid的子進程退出了。注意這個用法和wait()
是不一樣的,後者只能等待任意一個子進程退出,而waitpid()
可以指定等待具體哪個進程。
第四部分 關於返回值pid_t的補充說明
- pid_t在頭文件types.h(sys/types.h)中定義。
- pid_t是一個typedef定義類型。
用它來表示進程id類型。
//sys/types.h:
typedef short pid_t; /* used for process ids */
由此看出,上述定義的pid_t就是一個short類型變量,實際表示的是內核中的進程表的進程號索引。
- 使用pid_t的一個好處——可移植性更好
比如對於不同的平臺,可以typedef int pid_t
,也可以typedef long pid_t
- 事實上Linux內核源碼的數據類型基本都把基本類型進行了一次
typedef
的封裝。
參考文獻
https://blog.csdn.net/csdn_kou/article/details/81091191
《嵌入式Linux應用開發標準教程》 華清遠見嵌入式培訓中心 編著
《深入理解計算機系統》 Randal E.Bryant, David R.O’Hallaron編著