一、控制终端

对话期和进程组有一些其他特性:

  • 一个对话期可以有一个单独的控制终端。通常是我们在其上登录的终端设备或伪终端设备。
  • 建立与控制终端连接的对话期首进程,被称之为控制进程
  • 一个对话期中的几个进程组可以被分成一个前台进程组以及一个或几个后台进程组
  • 如果一个对话期有一个控制终端,则它有一个前台进程组,其他进程组则为后台进程组。
  • 无论何时键入终端键(Ctrl-C)或退出键(Ctrl-\),就会造成中断信号或退出信号送至前台进程组的所有进程。
  • 如果终端界面检测到调制解调器已经脱开连接,则将挂断信号送至控制进程。

这些特性见下图
  

登录时会自动建立控制终端

二、tcgetpgrp和tcsetpgrp函数

以下两个函数用来通知内核哪一个进程组是前台进程组,这样,终端设备驱动程序就能了解将终端输入和终端产生的信号送到何处。

#include <sys/types.h>
#include <unistd.h> pid_t tcgetpgrp(int filedes);
返回值:成功返回前台进程组ID,出错-1
int tcsetpgrp(int filedes, pid_t pgrpid);
返回值:成功为0,出错为-1

函数tcgetpgrp返回前台进程组ID,它与在filedes上打开的终端相关。
  如果进程有一个控制终端,则该进程可以调用tcsetpgrp将前台进程组ID设置为pgrpid。pgrpid值应当是在同一会话期中的一个进程组的ID。filedes必须引用该对话期的控制终端。
  大多数程序并不直接调用这两个函数。它们通常由作业控制shell调用。只有定义了—_POSIX_JOB_CONTROL,这两个函数才被定义了。否则它们返回出错。

三、作业控制

作业控制允许在一个控制终端上启动多个作业(进程组),控制哪一个作业可以存取该终端,以及哪些作业在后台运行。作业控制要求三种形式的支持:

  • 支持作业控制shell。
  • 内核中的终端驱动程序必须支持作业控制
  • 必须提供对某些作业控制信号的支持

一个作业只是几个进程的集合,通常是一个进程管道。例如:

vim abc.c

在前台启动了只有一个进程的一个作业。下面的命令:

pr *.c | lpr &
make all &

在后台启动了两个作业。这两个作业所调用的进程都在后台运行。   当启动一个后台作业时,shell赋予它一个作业标识。并打印一个或几个进程ID。下面的操作过程显示了KornShell是如何处理这一点的。

$ make all > Make.out &
[1] 1475
$ pr *.c | lpr &
[2] 1490
$ 键入回车
[2] + Done pr *.c | lpr &
[1] + Done make all > Make.out &

make是作业号1,所启动的进程ID是1475.下一个管道线是作业号2,其中第一个进程的进程ID是1490。当作业已完成并且键入回车时,shell通知我们作业已完成。键入回车是为了让shell打印其提示符。shell并不在任意时间打印后台作业的状态改变,它只在打印其提示符之前这样做。
  有三个特殊字符可使终端驱动程序产生信号,并将他们送至前台进程组,后台进程组作业不受影响。它们是:

  • 终端字符(一般用DELETE或者Ctrl-C)产生SIGINT
  • 退出字符(一般用Ctrl-\)产生SIGOUT
  • 挂起字符(一般采用Ctrl-Z)产生SIGTSTP

如果后台作业试图读终端,终端驱动程序会检测这种情况,并且发送一个特定信号SIGTTIN给后台作业。这通常会停止此后台作业,而有关用户则会得到这种情况的通知,然后就可以将此作业转为前台作业运行,于是它就可以读终端。下面操作过程展示了这种情况:

$ cat > temp.foo &      在后台启动,但将从标准输入读
[1] 1681
$ 键入回车
[1] + Stopped (tty input) cat > temp.out &
$ fg &1 使1号作业成为前台作业
cat > temp.foo shell告诉我们现在哪一个作业在前台
hello, world 输入1行
^D 键入文件描述符
$ cat temp.foo 检查该行已送入文件
hello, world

shell在后台启动cat进程,但是当cat试图读其标准输入(控制终端)时,终端驱动程序知道它是后台作业,于是将SIGTTIN信号送至该后台作业。shell检测到其子进程的状态变化,并通知我们该作业已被停止。然后,用shell的fg命令将次停止的作业送入前台运行。这样做使shell将此作业转为前台进程组(tcsetpgrp),并将继续信号(SIGCONT)送给该进程组。因为该作业现在在前台进程组中,所以它可以读控制终端。
  如果后台进程输出到控制终端会发生什么呢?这是一个可以允许或禁止的选择项。通常,可以用stty命令来改变这一选项

$ cat temp.foo &            在后台运行
[1] 1719
$ hello, world 在提示符出现后台作业的输出
键入回车
[1] + Done cat temp.foo &
$ stty tostop 禁止后台作业向控制终端输出
$ cat temp.foo & 在后台再次运行
[1] 1721
$ 键入回车,发现作业已停止
[1] + Stopped(tty output) cat temp.foo &
$ fg %1 将停止的作业恢复为前台作业
cat temp.foo shell告诉我们现在哪一个作业在前台
hello, world 该作业的输出

下图摘录了我们已说明的作业控制的某些功能。穿过终端驱动程序的实线表示:终端I/O和终端产生的信号总是从前台进程组连接到实际终端。对应于SIGTTOU信号的虚线表示后台进程组进程的输出是否出现在终端是可选择的。

  

四、shell执行程序

首先使用不支持作业控制的经典的Bourne shell。如果执行:

ps -xj

则其输出为: PPID PID PGID SID TPGID COMMAND 1 163 163 163 163 -sh 163 163 163 163 163 ps   结果略去了现在无关的列。shell和ps命令两者位于同一对话期和前台进程组(163)中。因为163是在TGPID列中显示的进程组,所以称其为前台进程组。
  说进程与终端进程组ID(TPGID列)相关联并不当。进程并没有终端进程控制组。进程属于一个进程组,而进程组属于一个对话期。对话期可能有,也可能没有控制终端。如果它确有一个控制终端,则此终端设备知道其前台进程的进程组ID。这一值可以用tcsetpgrp函数在终端驱动程序中设置。前台进程组ID是终端的一个属性,而不是进程的属性。取自终端设备驱动程序的该值是ps在TPGID列中打印的值。如果ps发现此对话期没有控制终端,则它在该列打印1。
  如果在后台执行命令:

ps -xj &

则唯一改变的值是命令的进程ID。

PPID  PID  PGID  SID  TPGID  COMMAND
1 163 163 163 163 -sh
163 163 163 163 163 ps

因为这种shell不知道作业控制,所以后台作业没有构成另一个进程组,也没有从后台作业处取走控制终端。
  看一下Bourne shell如何处理管道线。执行下列命令:

ps -xj | cat1

其输出是:

PPID  PID  PGID  SID  TPGID  COMMAND
1 163 163 163 163 -sh
163 200 163 163 163 cat1
200 201 163 163 163 ps

(程序cat1只是标准cat程序的一个副本,但名字不同)管道中最后一个进程是shell的子进程,该管道中的第一个进程则是最后一个进程的子进程。从中可以看出,shell fork一个它的副本,然后此副本再为管道线中的每条命令各fork一个进程。
 &nsbp;如果在后台执行此管道线:

ps -xj | cat1 &

则只有进程ID改变了。因为shell并不处理作业控制,后台进程的进程组ID仍是163,如果终端进程组ID一样。
  在没有作业控制时如果后台作业试图读控制终端,其处理方法是:如果该进程自己不重新定向标准输入,则shell自动将后台进程的标准输入重新定向到/dev/null。读/dev/null则产生一个文件结束。这就意味着后台cat进程立即读到文件尾,并正常结束。
  在一条管道中执行三个进程:

ps -xj | cat1 | cat2

该管道中的最后一个进程是shell的子进程,而执行管道中其他命令的进程则是该最后进程的子进程。下图展示了所发生的情况:
  

五、孤儿进程组

一个父进程已终止的子进程称为孤儿进程(orphan process),这种进程由init进程收养。整个进程组也可以成为孤儿。   考虑一个进程,它fork了一个子进程然后终止。这在系统中是进场发生的,但是在父进程终止时,如果该子进程停止(用作业控制)该如何?下面的程序就是这种情况的一个例子。下图显示了程序已经启动,父进程已经fork了子进程之后的情况。
  

#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include "ourhdr.h" static void sig_hup(int);
static void pr_ids(char *); int main(void)
{
char c;
pid_t pid; pr_ids("parent");
if ( (pid = fork()) < 0) {
fprint(stderr, "fork error\n");
exit(1);
} else if (pid > 0) {
sleep(5); // sleep 5等待子进程退出
exit(0); // 父进程退出
} else { // 子进程
pr_ids("child");
signal(SIGHUP, sig_hup); //
kill(getpid(), SIGTSTP);
pr_ids("child");
if (read(0, &c, 1) != 1) {
printf("read error from control terminal,errno = %d\n", errno);
}
exit(0);
}
} static void sig_hup(int signo) {
printf("SIGHUP received, pid = %d\n, getpid()");
return ;
} static void pr_ids(char *name) {
printf("%s: pid = %d, ppid = %d, pgrp = %d\n", name, getpid(), getppid(), getpgrp());
fflush(stdout);
}

  

这里假定使用了一个作业控制shell。shell将前台进程放在一个进程组中(本例是512),shell则留在自己的组内(442)。子进程继承其父进程512进程组。在fork后:

  • 父进程睡眠5秒,让子进程在父进程终止之前运行
  • 子进程为挂断信号(SIGHUP)建立信号处理程序。
  • 子进程用kill函数向其自身发送停止信号SIGTSTP。这停止了子进程,类似于用终端挂起字符(Ctrl-Z)停止一个前台作业。
  • 当父进程终止时,该子进程成为孤儿进程,其父进程ID成为1,也就是init进程ID。
  • 现在,子进程成为一个孤儿进程组的成员。POSIX.1将孤儿进程组定义为:该组中每一个成员的父进程或者是该组中的一个成员,或者不是该组所属对话期的成员。
  • 因为在父进程终止后,进程组成为孤儿进程组,POSIX.1要求向新孤儿进程组中处于停止状态的每一个进程发送挂断信号(SIGHUP),接着又向其发送继续信号(SIGCONT)。
  • 在处理了挂断信号后,子进程继续。对挂断信号的系统默认动作是终止该进程,为此必须提供一个信号处理函数来捕捉此信号。因此我们期望sig_hup函数中的printf会在pr_id函数中的printf之前执行。

下面是程序的输出:
  

因为两个进程,登录shell和子进程都写向终端,所以shell提示符和子进程的输出一起出现。
  在子进程中调用pr_ids后程序企图读标准输入。正如前述,当后台进程组试图读控制终端时,则对该后台进程组产生SIGTTIN。但在这里这是一个孤儿进程组,如果内核用此信号终止它,则此进程组中的进程就再也不会继续。POSIX.1规定,read返回出错,其errno设置为EIO。
  在父进程终止时,子进程变成后台进程组,因为父进程是由shell作为前台作业执行的。

[APUE]进程关系(下)的更多相关文章

  1. [APUE]进程关系(上)

    一.终端登录 1. 4.3+BSD终端登录 系统管理员创建一个通常名为/etc/ttys的文件,其中,每个终端设备有一行,每一行说明设备名和传到getty程序的参数,这些参数说明了终端的波特率.当系统 ...

  2. [APUE]进程控制(下)

    一.更改用户ID和组ID 可以用setuid设置实际用户ID和有效用户ID.可以用setgid函数设置实际组ID和有效组ID. #include <sys/types.h> #includ ...

  3. (七) 一起学 Unix 环境高级编程(APUE) 之 进程关系 和 守护进程

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

  4. 《UNIX环境高级编程》(APUE) 笔记第九章 - 进程关系

    9 - 进程关系 GitHub 地址 1. 进程组 每个进程除了有一个 进程 ID 外,还属于一个 进程组 .进程组是一个或多个进程的 集合 ,通常,它们是在同一作业中结合起来的,同一进程组中的各进程 ...

  5. Linux进程关系

    Linux进程关系   作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! Linux的进程相互之间有一定的关系.比如说,在Linux ...

  6. JAVA_SE基础——50.接口关系下的多态

    接口关系下的多态和继承关系下的多态 相差无几,应该更简单些~ 多态: 父类的引用类型变量指向了子类的对象或者是接口类型的引用类型变量指向了接口实现类 的对象. 实现关系下的多态: 接口  变量  = ...

  7. Linux 进程(二):进程关系及其守护进程

    进程关系 进程组 进程组是一个或多个进程的集合.通常,它们是在同一作业中结合起来的,同一进程组中的各进程接收来自同一终端的各种信号,每个进程组有一个唯一的进程组ID.每个进程组有一个组长进程,该组长进 ...

  8. 继承关系下的this关键字

    继承关系下的this关键字 在继承关系下,父类中的this关键字并不总是表示父类中的变量和方法.this关键字的四种用法如前文所述,列举如下. 1) this(paras…); 访问其他的构造方法 2 ...

  9. java 接口实现关系下的多态

    2019独角兽企业重金招聘Python工程师标准>>> 多态: 父类的引用类型变量指向了子类的对象 或者 是接口类型的引用类型变量指向了接口实现类的对象. 实现关系下的多态:    ...

随机推荐

  1. c# sqlite 数据库加密

    c# sqlite 数据库加密 2010-05-29 10:55 用了ADO.NET 2.0 SQLite Data Provider这样可以直接利用它来创建一个加密的sqlite数据库.有关c#代码 ...

  2. Android开源项目SlidingMenu学习(二)

    前一篇SlidingMenu学习(一)文章中了解了导入SlidingMenu到我们项目经常出现的问题,下面我们正式学习. 先看一个效果: 看到两幅图片的差别了吗,左边的一栏时可以滑动的,可以隐藏掉,现 ...

  3. ActiveX学习笔记二 ActiveX在IE中安全级别问题-实现IObjectSafety接口

    http://blog.csdn.net/freedomqx/article/details/4955512 使用MFC开发ActiveX控件,在IE中会提示安全问题,这个可以通过实现IObjectS ...

  4. 你的项目真的需要Session吗? redis保存session性能怎么样?

    在web开发中,Session这个东西一直都很重要,至少伴随我10年之久, 前一段时间发生一个性能问题,因为Redis session 问题,后来想想 其实我的项目session 是不需要的. 先看看 ...

  5. 用户人品预测大赛--就是gan队--竞赛分享

     用户人品预测大赛--就是gan队--竞赛分享  DataCastle运营 发表于 2016-3-24 14:14:05      1194  1  0 答辩PPT

  6. C++开源项目等收集

    VLC 是一款自由.开源的跨平台多媒体播放器及框架,可播放大多数多媒体文件,以及 DVD.音频 CD.VCD 及各类流媒体协议. Downloading vlc-2.2.4.tar.xz Thanks ...

  7. 添加JavaDoc

    使用javadoc比较容易生成文档,命令如下: javadoc -d doc -sourcepath src/main/java/ -subpackages com -encoding UTF-8 - ...

  8. python2中在sqlite3中插入中文

    # -*- coding: utf-8 -*- import sqlite3 conn = sqlite3.connect('SWC_Perf_Info.db') cur = conn.cursor( ...

  9. .NET 同步与异步 之 原子操作和自旋锁(Interlocked、SpinLock)(九)

    本随笔续接:.NET 同步与异步之锁(ReaderWriterLockSlim)(八) 之前的随笔已经说过.加锁虽然能很好的解决竞争条件,但也带来了负面影响:性能方面的负面影响.那有没有更好的解决方案 ...

  10. Starting httpd: httpd: Could not reliably determine the server's fully qualified domain name

    启动apache的时候,报告以下消息提示: Starting httpd: httpd: Could not reliably determine the server's fully qualifi ...