每一個進程除了有一個進程ID外,還屬於一個進程組。 進程組是一個或多個進程的集合,通常情況下,他們是在同一作業中結合起來的,同一進程組的個進程接受來自同一終端的各種信號。 每一個進程組有一個唯一的進程ID。
組長進程
每個進程組都有一個組長進程,組長進程的進程組ID等於其進程ID。 進程組組長可以創建一個進程組,創建進程組中的進程然后種植。只要進程組中還有任意一個進程存在,那么這個進程組就存在。 從進程組的創建到最后一個進程離開的時間去成為進程組的生命周期
函數getpgrp返回調用進程的進程組ID。
#include<unistd.h>
pid_t getpgrp(void);
下面的代碼來驗證下進程組
void pgroup_func(){
pid_t pid;
pid=fork();
if (pid == 0){;
printf("child id is:%d, the group id is :%d\n",getpid(),getpgrp());
sleep(1);
}
else{
printf("parent id is:%d,the group id is:%d\n",getpid(),getpgrp());
sleep(1);
}
}
從下面的運行結果可以看出,子進程和父進程的進程組ID是一樣的。說明子進程和父進程同屬一個進程組。父進程是這個進程組的組長。
那么如何修改進程組呢,setpgid函數將pid進程的進程組ID設置為pgid. 如果這兩個參數相等,則由pid指定的進程變成進程組組長。如果pid是0,則使用調用者的進程ID。如果pgid=0,則由pid指定的進程ID用做進程組ID。但是如果子進程一旦執行exec,父進程就無法調用setpgid函數來設置子進程的進程組ID了
代碼增加一個setpgid
void pgroup_func(){
pid_t pid;
pid=fork();
if (pid == 0){
setpgid(pid,0);
printf("child id is:%d, the group id is :%d\n",getpid(),getpgrp());
sleep(1);
}
else{
printf("parent id is:%d,the group id is:%d\n",getpid(),getpgrp());
sleep(1);
}
}
可以看到父進程和子進程的進程組ID不一樣了。
有了創建進程組的接口,新創建的進程組就不必繼承父進程的進程組ID了。最常見的創建進程組的場景就是在shell中執行管道命令,代碼如下:cmd1 | cmd2 | cmd3
下面用一個最簡單的命令來說明,其進程之間的關系如下所示。
ps ax|grep nfsd
ps進程和grep進程都是bash創建的子進程,兩者通過管道協同完成一項工作,它們隸屬於同一個進程組,其中ps進程是進程組的組長。
進程組的概念並不難理解,可以將人與人之間的關系做類比。一起工作的同事,自然比毫不相干的路人更加親近。shell中協同工作的進程屬於同一個進程組,就如同協同工作的人屬於同一個部門一樣。
引入了進程組的概念,可以更方便地管理這一組進程了。比如這項工作放棄了,不必向每個進程一一發送信號,可以直接將信號發送給進程組,進程組內的所有進程都會收到該信號。
前面提到過,子進程一旦執行exec,父進程就無法調用setpgid函數來設置子進程的進程組ID了,這條規則會影響shell的作業控制。出於保險的考慮,一般父進程在調用fork創建子進程后,會調用setpgid函數設置子進程的進程組ID,同時子進程也要調用setpgid函數來設置自身的進程組ID。這兩次調用有一次是多余的,但是這樣做能夠保證無論是父進程先執行,還是子進程先執行,子進程一定已經進入了指定的進程組中。由於fork之后,父子進程的執行順序是不確定的,因此如果不這樣做,就會造成在一定的時間窗口內,無法確定子進程是否進入了相應的進程組。
會話
會話是一個或多個進程組的集合。進程調用setsid函數建立一個新會話。
如果調用此函數的進程不是一個進程組的組長,則此函數就會創建一個新會話,該進餐變成會話的首進程,然后該進程成為一個新進程組的組長進程,該進程沒有控制終端。因為會話首進程是具有唯一進程ID的單個進程,所以可以將會話首進程的進程ID視為會話Id。
#include <unistd.h>
pid_t setsid(void);
pid_t getsid(pid_t pid);
來看下面的2個例子:
void session_func(){
pid_t pid;
pid=fork();
if(pid == 0){
printf("child id is:%d, the group id is:%d,the session id is:%d\n",getpid(),getpgrp(),getsid(pid));
}
else{
printf("parent id is:%d,the group id is:%d,the session id is:%d\n",getpid(),getpgrp(),getsid(pid));
}
}
在子進程中創建會話
void session_func(){
pid_t pid;
pid=fork();
if(pid == 0){
setsid();
printf("child id is:%d, the group id is:%d,the session id is:%d\n",getpid(),getpgrp(),getsid(pid));
}
else{
printf("parent id is:%d,the group id is:%d,the session id is:%d\n",getpid(),getpgrp(),getsid(pid));
}
}
}
我們可以看到在子進程沒用setsid函數建立一個會話之前,子進程是和父進程在同一會話里的,當子進程用setsid函數建立一個會話,會話的首進程ID就是子進程ID也就是會話ID。
一個會話可以有一個控制終端。這通常是登陸到其上的終端設備(在終端登陸情況下)或偽終端設備(在網絡登陸情況下)。建立與控制終端連接的會話首進程被稱為控制進程。
一個會話中的幾個進程組可被分為一個前台進程組以及一個或多個后台進程組。所以一個會話中,應該包括控制進程(會話首進程),一個前台進程組和任意后台進程組。
無論何時鍵入終端的終端鍵,都會將中斷信號發送到前台進程組的所有進程
還是來看之前的例子,
void session_func(){
pid_t pid;
pid_t pid1;
pid=fork();
pid1=tcgetpgrp(0);
printf("The pid1 is %d\n",pid1);
if(pid == 0){
setsid();
printf("child id is:%d, the group id is:%d,the session id is:%d\n",getpid(),getpgrp(),getsid(pid));
}
else{
printf("parent id is:%d,the group id is:%d,the session id is:%d\n",getpid(),getpgrp(),getsid(pid));
}
}
tcgetpgrp的原型如下,通過終端的文件描述符fd返回前台進程組pid.
#include<unistd.h>
pid_t tcgetpgrp(int fd)
通過tcgetpgrp(0)得到終端1的pid,可以看到和父進程的pid是一樣的。