进程组、会话和作业控制(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

34.2 进程组(Process Groups)

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

#include <unistd.h>
pid_t getpgrp (void);
Always successfully returns process group ID of calling process

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

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

#include <unistd.h>
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 <unistd.h>
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 <unistd.h>
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 <unistd.h>
#include <fcntl.h>
#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);
}
–––––––––––––––––––––––––––––––––––––––––––––––––––––––––

34.4  控制终端 和 控制进程

会话在被创建出来的时候 是没有控制终端的,当会话 leader进程 首次打开一个还没有 成为某个会话的控制终端 的 终端 时,会建立控制终端。 一个终端至多只能成为一个会话的控制终端。

控制终端 会被fork() 创建的子进程继承 并且在exec() 调用中 得到保持。

当会话leader进程 打开了一个控制终端之后 它同时也成为了该终端的控制进程。 在发生终端断开之后,内核会向控制进程 发送一个 SIGHUP 信号 来通知这一事件的 发生。

如果一个进程 拥有一个控制终端, 那么打开特殊文件 /dev/tty 就能够 获取该终端的文件描述符。

如果进程 没有控制终端,那么在打开/dev/tty 时 会报出 ENXIO的错误。

删除 进程 和 控制终端之间的关联关系

使用ioctl(fd,  TIOCNOTTY) 操作 能够删除进程 与文件描述符 fd 指定的 控制好终端的关联 关系。 在调用这个函数之后 再试图 打开 /dev/tty 文件的 话就会失败。

如果调用进程 是终端的控制进程 ,会发生如下事情:

3.  内核会向 前台进程组的 所有成员 发送一个 SIGHUP 信号(和一个SIGCONT 信号)来通知 它们 控制终端的丢失。

获取表示控制终端 的路径名: ctermid()

#include <stdio.h> /* Defines L_ctermid constant */
char * ctermid (char * ttyname );
Returns pointer to string containing pathname of controlling terminal,
or NULL if pathname could not be determined

 

引入这个函数的目的 是为了能更加容易地 将程序移植 (portability)到 非 UNIX 系统上。

34.5 Foregroud and Backgroud Process Groups (前台 和 后台 进程组)。

前台进程组 是唯一能够 自由地读取 和写入 控制终端的进程组。 

从理论上来讲,可能会出现一个会话没有前台进程组的情况。但在实践情况中是比较少见的。通常shell 进程会监控前台进程组的状态,当它注意到前台进程组结束之后(通过wait()) 会将自己移动到前台。

tcgetpgrp()  和 tcsetpgrp() 函数分别 获取和修改  一个终端 的进程组。这些函数主要供  任务控制shell 使用。

#include <unistd.h>
pid_t tcgetpgrp (int fd );
Returns process group ID of terminal’s foreground process group,
or –1 on error
int tcsetpgrp (int fd , pid_t pgid );
Returns 0 on success, or –1 on error

tcgetpgrp() 函数返回文件描述符fd 所指定的终端  的前台进程组的 进程组ID, 该终端必须是调用进程的 控制终端。

tcgetpgrp() 和 tcsetpgrp() 在SUSv3 中都被标准化了。在Linux 上,与很多其它UNIX 实现一样,这些函数是通过两个非标准的ioctl()操作来实现的,即 RIOCGPGRP  和 TIPCSPGRP

34.6 SIGHUP 信号

当一个控制进程失去其  终端连接后,内核会向其发送一个SIGHUP 信号来通知它这一事实。(还会发送一个 SIGCONT 信号  以确保 当该进程之前被一个信号 停止时  重新开始该进程)。

SIGHUP 信号的默认处理方式 是 终止进程。如果控制进程处理了 或 忽略了这个信号,那么后续尝试 从终端中读取 数据的请求 就会返回文件结束的 错误。

向 控制进程 发送SIGHUP 信号会引发一种链式反应, 从而导致 将 SIGHUP 信号发送给很多其它进程 。 这个过程可能会 以下列两种方式发生。

1. 控制进程通常是一个shell .shell 建立了一个SIGHUP 信号的处理器,这样在进程终止之前,它能够 将SIGHUP 信号发送给由它所创建 的各个任务。在默认情况下,这个信号会终止那些任务,但如果它们捕获了 这个信号,就能知道shell 进程已经终止了。

2. 在终止 终端的 控制进程 时,内核会解除会话中 所有进程  与 该控制终端 之间的关联关系以及 控制终端与该会话的关联关系(因此 另一个会话 leader进程可以 请求该终端 成为控制终端了),并且通过向 该终端 的前台进程组的成员 发送 SIGHUP 信号来通知它们 控制终端 的丢失。

 

SIGHUP 信号 也可以用作他用。 当一个进程组成为 孤儿进程组时 会生成SIGHUP信号。此外,手工发送SIGHUP 信号通常用来触发 daemon 进程重新初始化自身 或重新读取其配置文件。

34.6.1 在shell 中处理 SIGHUP 信号

在登录会话中,shell 通常是终端的控制进程。大多数shell 程序在交互运行时 会为SIGHUP 信号建立一个处理器。这个处理器会终止 shell, 但在终止之前 会向由shell 创建的各个进程组(包括前台和后台进程组)发送一个SIGHUP信号。(在SIGHUP信号之后可能会发送一个SIGCONT信号,这依赖shell 本身 以及任务当前是否处于停止状态) 。至于 这些组中的进程 如何响应 SIGHUP信号 需要根据应用程序的 具体需求,如果不采取特殊的动作, 那么默认情况下将会终止进程。

nohup 命令 可以用来使一个命令对SIGHUP 信号免疫  -即 执行命令时将SIGHUP信号的处理 设置为 SIG_IGN。 bash 内置的命令 disown 提供了类似的功能。

例子:

说明了 shell 不会向 不是由它创建的进程组 发送 SIGHUP 信号,即使该进程组 与 shell 位于同一个会话中。

todo

 

34.6.2  SIGHUP  和 控制进程的 终止

 

程序清单34-4 演示了 

todo

 

34.7 作业控制(job Control)

作业控制允许一个shell 用户同时执行多个命令(作业),其中一个命令在前台运行,其余的命令在后台运行。

34.7.1 在shell 中使用作业控制。

当输入的命令 以&(ampersand) 符号时,该命令会作为后台任务运行,如下示例:

 

$ grep -r SIGHUP /usr/src/linux >x &
[1] 18932                                                            Job 1: process running grep has PID 18932
$ sleep 60 &
[2] 18934                                                            Job 2: process running sleep has PID 18934

 

shell 会为 后台的每个进程 赋 一个唯一的作业号(job number).  作业号后面的数字 是执行这个命令的进程ID   或 管道中 最后一个进程的进程ID.    会使用 %num 来引用作业,其中num 是 shell 赋给作业的 作业号。

在很多情况下 是可以省略%num的,当省略%num 时默认指当前作业。当前作业是在前台最新被停止的作业(使用下面介绍的挂起字符)。

另外%% 和 %+ 符号指的是 当前作业, %- 符号指的是 上一个当前作业。 jobs  命令 的输出中 当前的 和上一个当前作业 分别 用 + 和 - 标记,

$ jobs
[1]- Running grep -r SIGHUP /usr/src/linux >x &
[2]+ Running sleep 60 &
 

fg 将 后台作业 移动到 前台。

$ fg %1
grep -r SIGHUP /usr/src/linux >x

 

当 一个 job 正运行在 前台,我们可以 挂起它 使用 终端 挂起(suspend)字符(通常是 Control-Z), 它发送 SIGTSTP 信号 给 终端的前台进程组:

Type Control-Z
[1]+ Stopped grep -r SIGHUP /usr/src/linux >x

 

如果需要的话 ,可以使用fg 命令 在前台恢复这个作业 或 使用 bg命令 在后台恢复 这个命令。不管使用哪个命令恢复作业,shell 都会通过向 任务发送 一个 SIGCONT 信号来恢复 被停止的作业。

 

$ bg %1
[1]+ grep -r SIGHUP /usr/src/linux >x &

 

我们可以停止 一个 background job  通过 发送给它 一个 SIGSTOP 信号

$ kill -STOP %1
[1]+ Stopped grep -r SIGHUP /usr/src/linux >x
$ jobs
[1]+ Stopped grep -r SIGHUP /usr/src/linux >x
[2]- Running sleep 60 &
$ bg %1 Restart job in background
[1]+ grep -r SIGHUP /usr/src/linux >x &

 

当后台作业最后执行结束之后, shell 会在打印下一个shell 提示符(prompt:)之前先 打印一条消息。

Press Enter to see a further shell prompt
[1]- Done grep -r SIGHUP /usr/src/linux >x
[2]+ Done sleep 60
$

 

仅仅在 前台的 进程s 可以 从 控制终端中读取。 这个限制 阻止了 多个jobs  竞争读取终端输入。如果后台作业尝试从终端中读取输入,就会接收到 一个 SIGTTIN 信号。 SIGTTIN信号的默认处理动作是停止 作业。

在默认情况下,后台作业 是被允许向控制终端 输入内容的。但如果终端 设置了 TOSTOP 标记, 那么当后台作业 尝试 在终端上 输入时 会导致 SIGTTOU 信号的产生。 与 SIGTTIN 信号一样 , SIGTTOU 信号 会停止作业。

$ stty tostop Enable TOSTOP flag for this terminal
$ date &
[1] 19023
$
Press Enter once more to see job state changes displayed prior to next shell prompt
[1]+ Stopped date

 

We can then see the output of the job by bringing it into the foreground:
$ fg
date
Tue Dec 28 16:20:51 CEST 2010

34.7.2 作业控制的实现(Implementing Job Control)

标准要求 支持作业控制,这种支持所需的条件如下:

。。。

在 20.5 节中 曾经讲过, 信号一般只有 在 发送进程的  真实 或有效用户ID  与 接收进程 的 真实用户ID 或 保存的 set-user-ID 匹配时 才会被发送给进程, 但 SIGCONT  是这个规则的 一个例外。 内核允许 一个进程(如shell)  向 同一会话中的任意进程 发送 SIGCONT 信号,不管进程的验证信息是什么。 在SIGCONT 信号上放宽 这个规则 是有必要的, 这样当用户开始一个会修改自身的验证信息 的 set-user-ID 程序时,仍然能够在程序 被停止时 通过 SIGCONT 信号来恢复 这个进程的运行。

SIGTTIN  和 SIGTTOU 信号

SUSv3 对后台进程的 SIGTTIN  和 SIGTTOU 信号的产生规定了一些特殊情况:

。。。

Example program: demonstrating the operation of job control

程序 34-5 给出的程序 能看出来 shell 是 如何将命令 以管道连接的 形式组织进一个作业的(进程组)。

可以以管道的形式 运行 这个程序的多个 实例,如下面的例子:

$ ./job_mon | ./job_mon | ./job_mon
Listing 34-5: Observing the treatment of a process under job control

–––––––––––––––––––––––––––––––––––––––––––––––––––––––––– pgsjc/job_mon.c
#define _GNU_SOURCE /* Get declaration of strsignal() from <string.h> */
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include "tlpi_hdr.h"
static int cmdNum; /* Our position in pipeline */
static void /* Handler for various signals */
handler(int sig)
{
    /* UNSAFE: This handler uses non-async-signal-safe functions
      (fprintf(), strsignal(); see Section 21.1.2) */

     if (getpid() == getpgrp())              /* If process group leader */
        fprintf(stderr, "Terminal FG process group: %ld\n",
        (long) tcgetpgrp(STDERR_FILENO));
     fprintf(stderr, "Process %ld (%d) received signal %d (%s)\n",
        (long) getpid(), cmdNum, sig, strsignal(sig));
     /* If we catch SIGTSTP, it won't actually stop us. Therefore we
       raise SIGSTOP so we actually get stopped. */
     if (sig == SIGTSTP)
        raise(SIGSTOP);
}


int
main(int argc, char *argv[])
{
     struct sigaction sa;
     sigemptyset(&sa.sa_mask);
     sa.sa_flags = SA_RESTART;
     sa.sa_handler = handler;
     if (sigaction(SIGINT, &sa, NULL) == -1)
        errExit("sigaction");
     if (sigaction(SIGTSTP, &sa, NULL) == -1)
        errExit("sigaction");
     if (sigaction(SIGCONT, &sa, NULL) == -1)
        errExit("sigaction");

 /* If stdin is a terminal, this is the first process in pipeline:
 print a heading and initialize message to be sent down pipe */

     if (isatty(STDIN_FILENO)) {
        fprintf(stderr, "Terminal FG process group: %ld\n", (long) tcgetpgrp(STDIN_FILENO));

        fprintf(stderr, "Command PID PPID PGRP SID\n");
        cmdNum = 0;
 } else {                   /* Not first in pipeline, so read message from pipe */
     if (read(STDIN_FILENO, &cmdNum, sizeof(cmdNum)) <= 0)
          fatal("read got EOF or error");
 }

  cmdNum++;
  fprintf(stderr, "%4d %5ld %5ld %5ld %5ld\n", cmdNum,
          (long) getpid(), (long) getppid(),
          (long) getpgrp(), (long) getsid(0));

 /* If not the last process, pass a message to the next process */
 if (!isatty(STDOUT_FILENO))            /* If not tty, then should be pipe */
      if (write(STDOUT_FILENO, &cmdNum, sizeof(cmdNum)) == -1)
           errMsg("write");

   for(;;)                                 /* Wait for signals */
       pause();

}
–––––––––––––––––––––––––––––––––––––––––––––––––––––––––– pgsjc/job_mon.c

 

34.7.3 Handling Job-Control Signals 处理作业控制信号

由于对于大多数应用程序来讲 作业 控制的 操作是 透明的, 因此 它们无需对作业控制信号 采取特殊的 动作,  但像 vi 和 less 之类的进行屏幕处理的 程序 则是例外,因为它们需要控制 文本在终端上 的布局 和修改 各种终端设置.....

......

在 这种情况下,恰当的处理方式 是 让 SIGTSTP 信号处理器 再生成一个SIGTSTP 信号来停止进程。

Listing 34-6: Handling SIGTSTP
–––––––––––––––––––––––––––––––––––––––––––––––––– pgsjc/handling_SIGTSTP.c
#include <signal.h>
#include "tlpi_hdr.h"
static void /* Handler for SIGTSTP */
tstpHandler(int sig)
{
 sigset_t tstpMask, prevMask;
 int savedErrno;
 struct sigaction sa;
 savedErrno = errno; /* In case we change 'errno' here */
 printf("Caught SIGTSTP\n"); /* UNSAFE (see Section 21.1.2) */
 if (signal(SIGTSTP, SIG_DFL) == SIG_ERR)
 errExit("signal"); /* Set handling to default */
 raise(SIGTSTP); /* Generate a further SIGTSTP */
 /* Unblock SIGTSTP; the pending SIGTSTP immediately suspends the program */
 sigemptyset(&tstpMask);
 sigaddset(&tstpMask, SIGTSTP);
 if (sigprocmask(SIG_UNBLOCK, &tstpMask, &prevMask) == -1)
 errExit("sigprocmask");
 /* Execution resumes here after SIGCONT */
 if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1)
 errExit("sigprocmask"); /* Reblock SIGTSTP */
 sigemptyset(&sa.sa_mask); /* Reestablish handler */
 sa.sa_flags = SA_RESTART;
 sa.sa_handler = tstpHandler;
 if (sigaction(SIGTSTP, &sa, NULL) == -1)
 errExit("sigaction");
 printf("Exiting SIGTSTP handler\n");
 errno = savedErrno;
}
int
main(int argc, char *argv[])
{
 struct sigaction sa;
Process Groups, Sessions, and Job Control 725
 /* Only establish handler for SIGTSTP if it is not being ignored */
 if (sigaction(SIGTSTP, NULL, &sa) == -1)
 errExit("sigaction");
 if (sa.sa_handler != SIG_IGN) {
 sigemptyset(&sa.sa_mask);
 sa.sa_flags = SA_RESTART;
 sa.sa_handler = tstpHandler;
 if (sigaction(SIGTSTP, &sa, NULL) == -1)
 errExit("sigaction");
 }
 for (;;) { /* Wait for signals */
 pause();
 printf("Main\n");
 }
}
–––––––––––––––––––––––––––––––––––––––––––––––––– pgsjc/handling_SIGTSTP.c

 

当命令通过nohup(1) 被执行时会忽略SIGHUP 信号,这样就防止了当终端被挂断时 命令被杀死的情况的发生,因此应用程序不应该在该信号被忽略时 试图改变这个信号的处理动作。

34.7.4 Orphaned Process Groups(and SIGHUP Revisited) 孤儿进程组 (SIGHUP 回顾)

if (fork() != 0)                                             /* Exit if parent (or on error) */
exit(EXIT_SUCCESS);

 

假设在shell 中 执行一个包含上面这段代码的程序; 在父进程终止后,子进程不仅是一个孤儿进程,同时也是孤儿进程组的 一个成员。

创建孤儿进程组的步骤:

... 为了防止 上面描述的情况的发生, SUSv3 规定,如果一个进程组 变成了 孤儿进程组 并且 拥有很多已经停止执行的成员, 那么系统会向进程组中的所有成员发送一个SIGHUP 信号通知它们 已经与会话断开连接了,之后再发送一个 SIGCONT 信号 确保它们恢复执行。 如果孤儿进程组 不包含被停止的成员,那么就不会发送任何信号。

孤儿进程组中的 成员在调用 tcsetpgrp()函数 时会得到 ENOTTY 的错误。

Listing 34-7: SIGHUP and orphaned process groups
––––––––––––––––––––––––––––––––––––––––––––––– pgsjc/orphaned_pgrp_SIGHUP.c
#define _GNU_SOURCE /* Get declaration of strsignal() from <string.h> */
#include <string.h>
#include <signal.h>
#include "tlpi_hdr.h"
static void /* Signal handler */
handler(int sig)
{ q printf("PID=%ld: caught signal %d (%s)\n", (long) getpid(),
 sig, strsignal(sig)); /* UNSAFE (see Section 21.1.2) */
}
int
main(int argc, char *argv[])
{
 int j;
 struct sigaction sa;
 if (argc < 2 || strcmp(argv[1], "--help") == 0)
 usageErr("%s {s|p} ...\n", argv[0]);
 setbuf(stdout, NULL);            /* Make stdout unbuffered */
 sigemptyset(&sa.sa_mask);
 sa.sa_flags = 0;
 sa.sa_handler = handler;
    if (sigaction(SIGHUP, &sa, NULL) == -1)
        errExit("sigaction");
    if (sigaction(SIGCONT, &sa, NULL) == -1)
        errExit("sigaction");
   printf("parent: PID=%ld, PPID=%ld, PGID=%ld, SID=%ld\n",
              (long) getpid(), (long) getppid(),
              (long) getpgrp(), (long) getsid(0));
   /* Create one child for each command-line argument */
    for (j = 1; j < argc; j++) {
        switch (fork()) {
           case -1:
                errExit("fork");
           case 0:                          /* Child */
                printf("child: PID=%ld, PPID=%ld, PGID=%ld, SID=%ld\n",
                       (long) getpid(), (long) getppid(),
                       (long) getpgrp(), (long) getsid(0));
               if (argv[j][0] == 's') {            /* Stop via signal */
                   printf("PID=%ld stopping\n", (long) getpid());
                    Process Groups, Sessions, and Job Control 729
                   raise(SIGSTOP);
               } else {               /* Wait for signal */
                   alarm(60);         /* So we die if not SIGHUPed */
                   printf("PID=%ld pausing\n", (long) getpid());
                   pause();
               }
           _exit(EXIT_SUCCESS);
           default:                 /* Parent carries on round loop */
             break;
         }
 }
     /* Parent falls through to here after creating all children */

     sleep(3); /* Give children a chance to start */
     printf("parent exiting\n");
     exit(EXIT_SUCCESS); /* And orphan them and their group */
}
–––––––––––

 

 

$ echo $$ Display PID of shell, which is also the session ID
4785
$ ./orphaned_pgrp_SIGHUP s p
parent: PID=4827, PPID=4785, PGID=4827, SID=4785
child: PID=4828, PPID=4827, PGID=4827, SID=4785
PID=4828 stopping
child: PID=4829, PPID=4827, PGID=4827, SID=4785
PID=4829 pausing
parent exiting
$ PID=4828: caught signal 18 (Continued)
PID=4828: caught signal 1 (Hangup)
PID=4829: caught signal 18 (Continued)
PID=4829: caught signal 1 (Hangup)
Press Enter to get another shell prompt
$ ./orphaned_pgrp_SIGHUP p p
parent: PID=4830, PPID=4785, PGID=4830, SID=4785
child: PID=4831, PPID=4830, PGID=4830, SID=4785
PID=4831 pausing
child: PID=4832, PPID=4830, PGID=4830, SID=4785
PID=4832 pausing
parent exiting

 

在第二次运行中 创建了两个子进程,但它们都没有停止自身,因此当父进程退出后 不会发送任何信号。

 

34.8 总结

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值