15-总结
34.8 总结
会话和进程组(也称为作业)形成了进程的双层结构:会话是一组进程组的集合,进程组是一组进程的集合。会话首进程是使用setsid()创建会话的进程。类似的,进程组首进程是使用setpgid()创建进程组的进程。进程组中的所有成员共享同样的进程组ID(与进程组首进程的进程组ID一样),进程组中所有构成一个会话的进程拥有同样的会话ID(与会话首进程的ID一样)。每个会话可以拥有一个控制终端(/dev/tty),这个关系是在会话首进程打开一个终端设备时建立的。打开控制终端还会导致会话首进程成为终端的控制进程。
会话和进程组是用来支持shell作业控制的(尽管有时候在应用程序中会另作他用)。在作业控制中,shell是会话首进程和运行该shell的终端的控制进程。shell会为执行的每个作业(一个简单的命令或以管道连接起来的一组命令)创建一个独立的进程组,并且提供了将作业在3个状态之间迁移的命令。这三个状态分别是在前台运行、在后台运行和在后台停止。
为了支持作业控制,终端驱动器维护了包含控制终端的前台进程组(作业)相关信息的记录。当输入特定的字符时,终端驱动器会向前台作业发送作业控制信号。这些信号会终止或停止前台作业。
终端的前台作业的概念还用于仲裁终端I/O请求。只有前台作业中的进程才能从控制终端中读取数据。系统通过SIGTTIN信号的分送来防止后台作业读取数据,这个信号的默认动作是停止作业。如果设置了终端的TOSTOP标记,那么系统会通过SIGTTOU信号的发送来防止后台任务向控制终端写入数据,这个信号的默认动作是停止作业。
当发生终端断开时,内核会向控制进程发送一个SIGHUP信号通知它这件事情。这样的事件可能会导致一个链式反应,即向很多其他进程发送一个SIGHUP信号。首先,如果控制进程是一个shell(通常是这种情况),那么在终止之前,进程会向所有由其创建的进程组发送一个SIGHUP信号。第二,如果SIGHUP信号的分送导致了控制进程的终止,那么内核还会向该控制终端的前台进程组中的所有成员发送一个SIGHUP信号。
一般来讲,应用程序无需弄清楚作业控制信号,但执行屏幕处理操作的程序则是一种例外。这种程序需要正确处理SIGTSTP信号,在进程被挂起之前需要将终端特性重置为正确的值,而当应用程序在接收到SIGCONT信号而再次恢复时需要还原正确(特定于应用程序)的终端特性。
当一个进程组中没有一个成员进程拥有位于同一会话但不同进程组中的父进程时,就成了孤儿进程组。孤儿进程组是非常重要的,因为在这个组外没有任何进程能够监控组中所有被停止的进程的状态并总是能够向这些被停止的进程发送SIGCONT信号来重启它们。这样就可能导致这种被停止的进程永远残留在系统中。为了避免这种情况的发生,当一个拥有被停止的成员进程的进程组变成孤儿进程组时,进程组中的所有成员都会收到一个SIGHUP信号,后面跟着一个SIGCONT信号,这样就能通知它们变成了孤儿进程并确保重启它们。
更多信息
[Stevens & Rago,2005]的第9章介绍了与本章类似的内容,并描述了在登录期间与登录shell建立会话时所发生的步骤。glibc手册对于作业控制相关的函数和作业控制在shell中的实现进行了详细的描述。SUSv3对会话、进程组和作业控制进行了广泛的讨论。