APUE读书笔记-19伪终端(13)
(2)作业控制交互
如果我们在pty下面运行一个作业控制shell,那么它会正常地工作。例如:
pty ksh
在pty下面运行Korn shell,我们可以在这个新的shell下面运行这个程序,并且像我们使用登陆shell那样使用作业控制。但是,如果我们如果在pty下面运行一个不是作业控制的交互程序,例如:
pty cat
那么所有的东西会直到我们键入作业控制挂起字符的时候才会正常。在那个时候,作业控制字符被显示成^Z并且被忽略。在早期基于BSD的系统中,cat进程终止,pty进程也终止,然后我们回到原来的shell中去。为了了解究竟发生了什么事情,我们需要对所有的进程,它们的进程组,以及会话进行检查。下面的图就展示了运行pty cat的时候的情况。
对于pty cat的进程组和会话
session session
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +- - - - - - - -+
| process group process group | | process group |
| +- - - - - - -+ +- - - - - - - - - - - - - - - - - + | | +- - - - -+ |
| | +---------+ | | +------------+ +---------+ | | | | +-----+ | |
| | | login | | | | pty | | pty | | | | | | cat | | |
| | | shell | | | | parent | | child | | | | | | | | |
| | +---------+ | | +--|-------^-+ +--|---^--+ | | | | +-|-^-+ | |
| +- - - - - - -+ +- - | - - - -\- - - - - -|- / - - + | | +- -|-|- -+ |
+- - - - - - - - - - - - -|- - - - -\- - - - - | / - - - - + +- - - | | - - -+
| /---+---------+/ | |
+----v-----/----+\ | +------v-|------+
| terminal | \ | | terminal |
|line discipline| \ | |line discipline|
+----|-----^----+ \ | +------|-^------+
| | \----+--- | |
| | | \ | |
+----v-----|----+ +--v----\-+ +---v-|---+
| terminal | | PTY | | PTY |
| device driver | | master | | slave |
+-------^-------+ +--|----^-+ +---|-^---+
| | | v |
| v +<----------------+ |
| +----------------------->+
/----v---\
|user at a |
| terminal |
\--------/
当我们键入挂起字符(Control-z)的时候,从pty将终端(在pty父进程之下)切入raw模式起,这个字符会被cat下面的行规则模块识别。但是由于它本身是孤儿进程组,所以内核不会停止这个cat进程(具体需要参见第9章10节)。cat的父进程是pty父进程,但是却属于另外一个会话。
以前,各种实现对这个情况的处理有所不同。POSIX.1只是说SIGTSTP信号不能发送给进程。从4.3BSD继承过来的系统却发送了SIGKILL信号,这个信号进程甚至不能捕获。4.4BSD中,这个行为改变了,它遵从POSIX.1,4.4BSD不是发送SIGKILL,它会静静地忽略SIGTSTP信号(如果这个信号是默认的行为并且要发送给另外一个孤儿进程组中的进程)。
当我们使用pty运行一个作业控制shell的时候,通过这个新的shell发起的作业不会是一个孤儿进程组中的成员,因为作业控制shell本身属于同一个会话。这个时候,Control-Z的键入,就会通过shell发送给这个被shell发起的进程,而不是发送给shell本身。
有一个方法可以避免被pty发起的进程无法处理作业控制信号:为pty添加另外一个命令行标记,告诉它让它自己识别作业控制挂起字符(在pty子进程中),而不是任由这个字符通过其他的行规则。
(3)查看长时间运行的程序的输出
另外一个通过pty程序进行作业控制交互的例子就是本章第2节的"通过伪终端运行慢输出程序"图所示的例子。如果我们如下运行的程序输出很慢:
pty slowout > file.out &
pty进程会在子进程尝试读取它的标准输入(终端)的时候立即停止。原因就是,这个作业是一个后台作业,当它尝试访问终端的时候会导致作业控制停止。如果我们将标准输入重新定向,一边pty不会尝试从终端进行读取,如下:
pty slowout < /dev/null > file.out &
pty程序会立即停止,因为它在它的标准输入上面读取到一个文件结束符号并且终止。解决这个问题的方法就是使用-i选项,这个选项表示会忽略标准输入的文件结束符号:
pty -i slowout < /dev/null > file.out &
这个标记导致当遇到文件结束符号的时候,pty子进程(前面代码中的loop函数)exit,但是子进程不会告诉父进程它终止了。相反,父进程还是继续将PTY slave的输出拷贝到标准输出(这个例子中的file.out文件)。