进程组、会话和作业控制(process groups, sessions, and job control)-Linux系统编程手册

进程组和会话 是为支持shell 作业控制而定义的抽象概念。

34.1 概述(overview)

进程组:由一个或多个 进程组成,它们有同样的进程组标识符(PGID)。进程组ID 是一个数字,其类型与进程ID 一样(pid_t)。一个进程组 拥有 一个 process group leader 进程 ,  该进程是创建该组的进程,其进程ID为 该进程组的ID,新进程会继承其父进程 所属的进程组ID。

进程组拥有一个 生命周期(lifetime),  其开始时间为 leader进程 创建组的时刻, 结束时间为 最后一个成员退出组的时刻。一个进程可能会因为 加入了另外一个进程组而退出进程组 或因为终止而退出进程组。进程组leader进程 不需要是 最后一个离开进程组的。

会话是一组进程组的集合。会话中的所有进程 有相同的 会话标识符(SID), 会话标识符 是 pid_t 类型 的数字。会话leader进程是创建该新会话的进程,其进程ID会成为会话ID。新进程会继承父进程的会话ID。

一个会话中的所有进程共享单个控制终端。 控制终端在 会话leader进程首次打开一个终端设备时被建立。 一个终端 最多 可能会成为 一个会话的控制终端。

在任一时刻,会话中的其中一个进程组会成为终端的前台进程组,其它进程组会成为后台进程组。只有前台进程组中的进程才能从控制终端中读取输入。当用户在控制终端中输入其中一个信号生成终端字符之后,该信号会被发送到前台进程组中的所有成员。这些字符包括生成SIGINT的中断字符(通常是Control-C), 生成SIGQUIT 的退出字符(通常是Control-\), 生成SIGSTP的挂起字符(通常是(Control-Z)。

图34.1 给出了执行下面的命令之后各个进程之间的进程组和会话关系。

$ echo $$ Display the PID of the shell
400
$ find / 2> /dev/null | wc -l & Creates 2 processes in background group
[1] 659
$ sort < longlist | uniq -c Creates 2 processes in foreground group

进程组、会话和作业控制(process groups, sessions, and job control)-Linux系统编程手册_第1张图片

34.2 进程组(Process Groups)

每个进程 都有一个进程组ID. 一个进程可以获取 它的进程组ID,通过使用getpgrp()

#include
pid_t getpgrp (void);
Always successfully returns process group ID of calling process

如果getpgrp() 返回的值和 调用者自己的 进程ID 相等, 那么这个进程 就是 它的进程组的 leader.

setpgid() 系统调用 可以改变 某个进程的 进程组ID

#include
int setpgid (pid_t pid , pid_t pgid );
Returns 0 on success, or –1 on error

说明 如果pid 被设置为 0, 则调用者的进程组ID 被改变。 如果pgid 被指定为0,则 由pid参数指定的进程 的进程组ID 会变的 和它的进程ID 一样。因此 如下的 setpgid() 是等效的。

setpgid(0, 0);
setpgid(getpid(), 0);
setpgid(getpid(), getpid());

 

调用setpgid() 时有几个约束

pid 参数 可以 指定为 调用进程的 或者 它的子进程的 pid. 违反这条规则 会导致 ESRCH 

调用进程 ,和 pid 指定的进程(可能时同一个),和 目标的进程组 必须是 处于相同的会话 (session)。违反这条规则会导致 EPERM

pid 的 值 不能是 会话的leader 进程的pid值。违反这条规则 导致 EPERM

一个进程在其子进程已经执行exec()后就无法修改子进程的进程组ID了。违反这条规则会导致 EACCESS错误。之所以会有这条约束是因为:在一个进程开始执行之后,再修改其进程组ID的话 会使程序变的混乱。

在作业控制shell 中 使用setpgid()

由于一个进程在其子进程已经执行exec()之后就无法修改该子进程的进程组ID 的约束条件。所以基于shell 的作业控制程序设计,要满足如下条件

1. 一个任务(一个命令或一组以管道符连接的命令)中的所有进程必须放置在一个进程组中。

2. 每个子进程在执行程序之前必须要被分配到进程组中,因为程序本身是不清楚如何操作进程组ID的。

Listing 34-1: How a job-control shell sets the process group ID of a child process
 pid_t childPid;
 pid_t pipelinePgid; /* PGID to which processes in a pipeline
                       are to be assigned */
 /* Other code */
 childPid = fork();
 switch (childPid) {
 case -1: /* fork() failed */
      /* Handle error */
 case 0: /* Child */
      if (setpgid(0, pipelinePgid) == -1)
          /* Handle error */
          /* Child carries on to exec the required program */
 default: /* Parent (shell) */
      if (setpgid(childPid, pipelinePgid) == -1 && errno != EACCES)
          /* Handle error */
          /* Parent carries on to do other things */
 }

获取和修改 进程组ID 的其他(过时的)接口。

setpgrp(pid, pgid);    //它能将将进程组ID 设置为任意值。这会引起安全问题。

getpgid(pid);   //

 

34.3  会话(Sessions)

The getsid() 系统调用 返回  由pid 指定的 的进程的 会话ID。

#define _XOPEN_SOURCE 500
#include
pid_t getsid (pid_t pid );
Returns session ID of specified process, or (pid_t) –1 on error

如果 pid 被指定为0, getsid() 返回调用进程 的 会话ID。

 

如果调用进程不是 进程组首进程(leader),那么setsid() 会创建一个新会话。

#include
pid_t setsid (void);
Returns session ID of new session, or (pid_t) –1 on error

setsid()系统调用 创建一个 新的会话 如下:

调用者进程 变成 新会话的 leader进程, 也是 新进程组的 leader进程。 调用进程的的 进程组ID 和 会话ID 被设置为 和它的进程ID相同。

调用者进程 没有 控制终端。 所有之前到控制终端的连接都会被断开。

如果调用进程 是一个进程组的leader进程,那么setsid()调用就会报出EPERM错误。避免这个错误发生的最简单的方式是执行一个fork() 并让父进程终止以及 让子进程调用setsid()。由于子进程会继承父进程的进程组ID 并接收属于自己的唯一进程ID.

约束进程组首进程对setsid()的调用是有必要的。因为如果没有这个约束的话,进程组组长就能将其自身迁移到另一个新的会话中了, 这会破坏会话和进程组之间严格的两级层级,因此一个进程组的所有成员必须属于同一个会话。

Listing 34-2 演示了setsid() 的使用 ,为了检查 它不再有 控制终端, 这个程序尝试打开 file  /dev/tty .当运行此程序时,我们看到如下:

$ ps -p $$ -o 'pid pgid sid command' $$ is PID of shell
PID PGID SID COMMAND
12243 12243 12243 bash PID, PGID, and SID of shell
$ ./t_setsid
$ PID=12352, PGID=12352, SID=12352
ERROR [ENXIO Device not configured] open /dev/tty

 

––––––––––––––––––––––––––––––––––––––––––––––––––––––––– pgsjc/t_setsid.c
#define _XOPEN_SOURCE 500
#include 
#include 
#include "tlpi_hdr.h"
int
main(int argc, char *argv[])
{
   if (fork() != 0) /* Exit if parent, or on error */
        _exit(EXIT_SUCCESS);
    if (setsid() == -1)
       errExit("setsid");
    printf("PID=%ld, PGID=%ld, SID=%ld\n", (long) getpid(),
         (long) getpgrp(), (long) getsid(0));
    if (open("/dev/tty", O_RDWR) == -1)
       errExit("open /dev/tty");
    exit(EXIT_SUCCESS);
}
–––––––––––––––––––––––––––––––––––––––––––––––––––––––––

 

 

 

 

 

你可能感兴趣的:(读书笔记)