本章将详细地说明进程组以及POSIX.1引入的会话的概念。还将介绍登录shell和所有从登录shell启动的进程之间的关系
终端登录
BSD终端登录。系统管理者创建通常名为/etc/ttys的文件,其中每个终端设备都有一行,用来说明设备名和传到getty程序的参数。
当系统自举时,内核创建进程ID为1的进程(init进程)。init进程读取文件/etc/ttys,对每一个允许登录的终端设备调用一次fork,它所生成的子进程则exec getty程序,如下图所示:
getty对终端设备调用open函数,以读、写方式将终端打开。一旦设备被打开,则文件描述符0、1、2就被设置到该设备。然后getty输出“login:”之类的信息,并等待用户键入用户名。
当用户键入用户名后,getty的工作就完成了。然后它以类似于下列方式调用login程序:
execle("/bin/login","login","-p",username,(char *)0,envp);
下图显示了login刚被调用后这些进程的状态
如果用户正确登录,login就将完成如下工作:
1 将当前工作目录更改为该用户的起始目录(chdir)
2 调用chown更改该终端的所有权,是登录用户成为它的所有者
3 将该终端设备的访问权限改变成“用户读和写”
4 调用setgid及initgroups设置进程的组ID
5 用login得到的所有信息初始化环境:起始目录(HOME)、shell(SHELL)、用户名(USERNAME和LOGNAME)以及一个系统默认路径(PATH)
6 login进程更改为登录用户的用户ID(setuid)并调用该用户的登录shell,其方式类似于:execl("/bin/sh","-sh",(char *)0)
网络登录
在上节所述的终端登录中,init知道哪些终端设备科用来进行登录,并为每个设备生成一个getty进程。但是对网络登录情况则有所不同,事先并不知道将会有多少这样的登录。
BSD网络登录。作为系统启动的一部分,init调用一个shell,使其执行shell脚本/etc/rc。由此shell脚本启动一个守护进程inetd。
inetd等待TCP/IP连接请求到达主机,而当一个连接请求到达时,它执行一次fork,然后生成的子进程exec适当的程序。
TELNET是使用TCP协议的远程登录应用程序。下图显示了在执行TELNET服务进程中所涉及的进程序列
然后,telnetd进程打开一个伪终端设备,并用fork分成两个进程。父进程处理通过网络连接的通信,子进程则执行login程序。
进程组
每个进程除了有一进程ID之外,还属于一个进程组,进程组是一个或多个进程的集合,每个进程组有一个唯一的进程组ID。
可以使用getpgrp返回调用进程的进程组ID
#include <unistd.h> pid_t getpgrp(void);
每个进程组有一个组长进程。组长进程的进程组ID等于其进程ID
进程调用setpgid可以加入一个现有的进程组或者创建一个新进程组
#include <unistd.h>
int setgpid(pid_t pid,pid_t pgid);
setpgid函数将pid进程的进程组ID设置为pgid。如果这两个参数相等,则由pid指定的进程变成进程组组长。
一个进程只能为它自己或它的子进程设置进程组ID。在它的子进程调用exec后,它就不再更改该子进程的进程组ID。
会话
会话是一个或多个进程组的集合。
进程调用setsid函数建立一个新会话。
#include <unistd.h> pid_t setsid(void);
如果调用此函数的进程不是一个进程组的组长,则此函数创建一个新会话。具体会发生以下3件事。
1 该进程变成新会话的会话首进程
2 该进程成为一个新进程组的组长进程。新进程组ID是该调用进程的进程ID
3 该进程没有控制终端
如果该调用进程已经是一个进程组的组长,则此函数返回出错。getsid函数返回会话首进程的进程组ID
#include <unistd.h> pid_t getsid(pid_t pid); //如果pid是0,函数返回调用进程的会话首进程的进程组ID
控制终端
会话和进程组还有一些其他特性
1 一个会话可以有一个控制终端。这通常是终端设备(在终端登录情况下)或伪终端设备(在网络登录情况下)。
2 建立与控制终端连接的会话首进程被称为控制进程。
3 一个会话的几个进程组可被分成一个前台进程组以及一个或多个后台进程组。
4 如果一个会话有一个控制终端,则它有一个前台进程组,其他进程组为后台进程组。
5 无论何时键入终端的中断键(常常是Delete或Ctrl+C),都会将终端信号发送至前台进程组的所有进程。
5 无论何时键入终端的退出键(常常是Ctrl+),都会讲退出信号发送至前台进程组的所有进程。
6 如果终端接口检测到调制解调器(或网络)已经断开,则将挂断信号发送至终端进程(会话首进程)
这些特性示于下图中
作业控制
一个作业只是几个进程的集合,通常是一个进程管道。
例如:vi main.c 在前台启动了只有一个进程组成的作业。
而下面命令(命令后面加&可以是作业在后台运行):
pr *.c | lpr &
make all &
在后台启动了两个作业。这连个后台作业调用的所有进程都在后台运行。
当启动一个后台作业时,shell赋予它一个作业标识符,并打印一个或多个进程ID。下面演示这一点:
当作业完成而且键入回车时,shell通知作业已经完成。
只有前台作业接收终端输入,如果后台作业试图读终端,则向后台作业发送一个特定信号SIGTTIN。
该信号通常会停止此后台作业,而shell则向有关用户发送这种情况的通知,然后用户就可用shell命令将此作业转为前台作业运行,于是它就可读终端。下面将演示这一点:
使用stty命令可以允许或禁止后台作业输出到控制终端