Fork me on GitHub

进程组

进程组

每个进程除了有一进程ID外,还属于一个进程组

进程组是一个或多个进程的集合。通常,他们是在同一作业中结合起来的,同一进程组中的各进程接收来自同一终端的各种信号。每个进程组有一个唯一的进程组ID。进程组ID类似于进程ID—它是一个正整数,并可存放在pid_t数据类型中。函数getpgrp返回调用进程的进程组ID。

1
2
#include <unistd.h>
pid_t getpgrp(void); //调用进程的进程组ID

Single UNiX Specification定义了getpgid函数模仿此种运行行为。该函数的参数时pid,返回该进程的进程组ID。

1
2
#include <unistd.h>
pid_t getpgid(pid_t pid);

若pid是0,返回调用进程的进程组ID,于是,getpgid(0); 等价于 getpgrp();

1
2
3
4
5
6
7
8
9
#include "head.h"
int main(void){
printf("pid = %d,ppid = %d\n",getpid(),getppid()); //主进程的父进程是bash
printf("pgrp = %d\n",getpgrp());
printf("pgrp = %d\n",getpgid(0));
system("ps -O pgid"); //打印进程状态
exit(0);
}
1
2
3
4
5
6
7
8
[vrlive@iZ23chs2r19Z nine]$ ./getppid.o
pid = 1174,ppid = 1103 //1103为bash
pgrp = 1174
pgrp = 1174
PID PGID S TTY TIME COMMAND
1103 1103 S pts/1 00:00:00 -bash
1174 1174 S pts/1 00:00:00 ./getppid.o
1175 1174 R pts/1 00:00:00 ps -O pgid

每个进程组有一个组长进程。组长进程的进程组ID等于其进程ID。

进程组组长可以创建一个进程组、创建该组中的进程,然后终止。只要在某个进程组中有一个进程存在,则该进程组就存在,这与其组长进程是否终止无关。从进程组创建开始到其中最后一个进程离开为止的时间区间称为进程组的生命期。某个进程组中的最后一个进程可以终止,也可以转移到另一个进程组。

进程调用setpgid可以加入一个现有的进程组或者创建一个新进程组。

1
2
#include <unistd.h>
int setpgid(pid_t pid,pid_t pgid); //若成功,返回0;若出错,返回-1

setpgid 函数将pid进程的进程组ID设置为pgid。如果这两个参数相等,则由pid指定的进程变成进程组组长。如果pid是0,则使用调用者的进程ID。另外,如果pgid是0,则由pid指定的进程ID用作进程组ID。

一个进程只能为它自己或它的子进程设置进程组ID。在它的子进程调用了exec后,它就不再更改该子进程的进程组ID。

在大多数作业控制shell中,在fork之后调用此函数,使父进程设置其子进程的进程组ID,并且也使子进程设置其自己的进程组ID。在这来两个调用中有一个是冗余的,但让父进程和子进程都这样做可以保证,在父进程和子进程认为子进程已进入了该进程组之前,这确实已经发生了。如果不这样做,在fork之后,由于父进程和子进程运行的先后次序不确定,会因为子进程的组员身份取决于哪个进程首先执行而产生竞争条件。

会话

会话(session)是一个或多个进程组的集合。

进程调用setsid函数建立一个新会话。

1
2
#include <unistd.h>
pid_t setsid(void); //若成功,返回进程组ID,若出错,返回-1

如果调用此函数的进程不是一个进程组的组长,则此函数创建一个新会话。具体会发生以下3件事。

  1. 该进程变成新会话的会话首进程。此时,该进程是新会话中的唯一进程。
  2. 该进程成为一个新进程组的组长进程。新进程组ID是该调用进程的进程ID。
  3. 该进程没有控制终端。如果在调用setsid之前该进程有一个控制终端,那么这种联系也被切断。

如果该调用进程已经是一个进程组的组长,则此函数返回出错。为了保证不处于这种情况,通常先调用fork,然后使其父进程终止,而子进程继续。因为子进程继承了父进程的进程组ID,而其进程ID则是新分配的,两者不可能相等,这就保证了子进程不是一个进程组的组长。

getsid函数返回会话首进程的进程组ID。

1
2
#include <unistd.h>
pid_t getsid(pid_t pid); //若成功,返回会话首进程的进程组ID;若出错,返回-1

如若pid是0,getsid返回调用进程的会话首进程的进程ID。处于安全方面的考虑,一些实现有如下限制:如若pid并不属于调用者所在的会话,那么调用进程就不能得到该会话首进程的进程组ID。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "head.h"
int main(void){
pid_t pid;
if((pid = fork()) < 0){
printf("fork error\n");
}else if(pid == 0){
pid_t sid;
sid = getsid(0);
printf("in child(before setsid) sid = %d\n",sid);
sid = setsid();
printf("in child(after setsid) sid = %d\n",sid);
printf("in child sid = %d,pgrpid = %d,pid = %d\n",sid,getpgrp(),getpid());
exit(0);
}
pid_t sid;
sid = setsid(); //error -1
printf("in parent sid = %d,pgrpid = %d,pid = %d\n",sid,getpgrp(),getpid());
sid = getsid(0);
printf("in parent sid = %d\n",sid);
sid = getsid(pid);
printf("in parent get child sid = %d\n",sid);
sid = getsid(1);
printf("in parent get init sid = %d\n",sid);
waitpid(pid,NULL,0);
return 0;
}
1
2
3
4
5
6
7
8
[vrlive@iZ23chs2r19Z nine]$ ./session.o
in parent sid = -1,pgrpid = 1469,pid = 1469
in parent sid = 1329
in parent get child sid = 1329
in parent get init sid = 1
in child(before setsid) sid = 1329
in child(after setsid) sid = 1470
in child sid = 1470,pgrpid = 1470,pid = 1470

在父进程中,调用setsid,因为该进程是进程组组长(pid和pgrpid都是1469),所以返回-1。

获取父进程sid是1329。

在父进程中获取子进程sid也是1329。

在父进程中获取init的sid是1。(好像能获取任何进程的sid)

在子进程中获取sid是1329(此时和父进程子在同一个会话中)

在子进程中setsid后,子进程处在新的会话中,新会话的ID是1470,

在子进程中获取sid是1470,进程组ID和进程ID都是1470,说明该子进程是会话中的第一个进程,而且是进程组的组长。

参考:https://blog.csdn.net/todd911/article/details/16965971

-------------本文结束感谢您的阅读-------------

本文地址:http://www.wangxinri.cn/2018/05/16/进程组和会话/
转载请注明出处,谢谢!

梦想夹带眼泪,咸咸的汗水!