进程组
进程组是一个或多个进程的集合。通常,它们是在同一作业中结合起来的,同一进程组中的各进程接受来自终端的各种信号。每个进程组有一个唯一的进程组 ID。进程组 ID 类似于进程 ID,它是一个正整数,并可存放在 pid_t 数据类型中。
函数getpgrp 返回调用进程的进程组 ID
#include <unistd.h>
pid_t getpgrp(void);
//返回值:调用进程的进程组 ID
pid_t getpgid(pid_t pid);
//返回值:若成功,返回进程组 ID;若出错,返回-1
对于 函数 getpgid 若pid 是 0,返回调用进程的进程组 ID,
每个进程组有一个组长进程。组成进程的进程组 ID 等于其进程 ID。
进程组组长可以创建一个进程组、创建该进程组中的进程,然后终止。只要在某个进程组中有一个进程存在,则该进程组就存在,这与其组长进程是否终止无关。进程组的生命期从进程组创建开始到其中最后一个进程离开为止的时间区间称为进程组的生命期。
进程调用 setpgid 可以加入一个现有的进程组或创建一个新的进程组
#include <unistd.h>
int setpgid(pid_t, 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 函数建立一个新的会话
#include<unistd.h>
pid_t setsid(void);
//返回值:若成功,返回进程组ID,若出错,返回-1
如果调用此函数的进程不是一个进程组的组长,则此函数创建一个新会话,具体发生以下 3 件事:
- 该进程变成新会话的会话首进程,此时,该进程是新会话中的唯一进程
- 该进程成为一个新进程组的组长进程。新进程组 ID 是该调用进程的进程 ID
- 该进程没有控制终端,如果在调用 setsid 之前该进程有一个控制终端,那么这种联系也被切断。
如果该调用进程已经是一个进程组的组长,则此函数返回出错。为了保证不处于这种情况,通常先调用 fork,然后使其父进程终止,而子进程则继续。因为子进程继承了父进程的进程组 ID,而其进程 ID 则是新分配的,两者不可能相等,这就保证了子进程不是一个进程组组长。
getsid 函数返回会话首进程的进程组 ID。
#include <unistd.h>
pid_t getsid(pid_t pid);
//返回值:若成功,返回会话首进程的进程组 ID;若出错,返回-1
如若 pid 是 0,getsid 返回调用进程的会话首进程的进程组 ID。出于安全方面的考虑,一些实现有如下限制:如若 pid 并不属于调用者所在的会话,那么调用进程就不能得到该会话首进程的进程组 ID。