(4)script程序
使用pty程序,我们可以如下通过shell脚本执行script程序:
#!/bin/sh
pty "${SHELL:-/bin/sh}" | tee typescript
当我们运行这个shell脚本的时候,我们可以执行ps命令来查看所有进程的关系,下面的途中就详细地列出了这些关系。
script的shell脚本的进程组织结构图
typescript
file -------------
^ / \
+-----------+ +----+ +--|--+ +----------+/ +---------+ \ +-----+ +----+
|login shell|-->| sh |-->| tee |-->|pty parent|--->|pty child| --->| ksh |--->| ps |
+-----------+ +----+ +-|-^-+ +--|---^---+ +--|---^--+ +-----+ +|--^+
| | pipe v \ / / | |
| +<-------+ \ / / | |
| /------------+------+--- +---v--|---+
+---v----|-+ \ | | line |
| line | \ / |discipline|
|discipline| \ / +---|--^---+
+---|-^----+ X | |
| | / \ | |
| | +-v---\-+ +-v--|-+
+-v-|-+ | PTY | | PTY |
| TTY | | master| |master|
+--^--+ +-|---^-+ +-|--^-+
| | | v |
| v +<---------------------------+ |
| +---------------------------------->+
/--v--\
| user |
\-----/
在这个例子里面,我们假设SHELL变量是Korn shell(一般来说导致使用/bin/ksh)。如我们前面所提到的那样,script只是拷贝这个新shell(以及它所发起的进程)所输出的内容。但是,当PTY slave上面的行规则模块一般来说打开回显的时候,大多数我们输入的也会写入到typescript文件中去。
(5)运行协作处理进程
在15章2节中"使用两个管道用于父子进程同步"这个图中,协作处理进程不能使用标准输入输出函数,因为标准输入输出不会引用终端,所以标准输入输出函数将它们看作是全缓冲的。如果我们在pty下面如下代码运行写作处理进程:
if (execl("./add2", "add2", (char *)0) < 0)
而不是:
if (execl("./pty", "pty", "-e", "add2", (char *)0) < 0)
那么程序即使在写作处理进程使用标准输入输出的时候,也会正常工作。
下图展示我们使用伪终端作为协作处理进程的输入输出的时候进程的运行情况。它实际上是对本章第2节的图"使用伪终端来驱动一个协作处理进程"的一个扩展,(为方便起见,我将它也贴在下面了,其实它和前面的图一样)它展示了所有的进程连接和数据流。标记了"driving program"的部分是来自在15章2节中"使用两个管道用于父子进程同步"图的程序,其中的execl如前面所说的进行了修改。
使用伪终端来驱动一个协作处理进程(扩展之前)
coprocess
+-----------+ pipe1 +-------------+ +-------------+
| driving |---------->| pseudo |----------->| stdin |
| program |<----------| terminal |<-----------| stdout |
+-----------+ pipe2 +-------------+ +-------------+
将伪终端作为协作处理进程的输入输出来运行写作处理进程(扩展后)
fork,exec
---------------------\
+-------------------+ fork +---------+ / +--------+ \ +-----------+
| driving |--------->| pty |/ fork | pty | \ | add2 |
| program | exec | parent |-------->| child | -->|(coprocess)|
+--|----^----|---^--+ +--|---^--+ +--/---^-+ +---|----^--+
| | | | pipe2 v \ / | | |
| | v +<---------------+ \ / | | |
| | +---------------------------\---------/----->+ | |
| | pipe1 \ / | |
+--v----|-------+ \ / +-----v----|----+
| terminal | \ / | terminal |
|line discipline| \ / |line discipline|
+--|----^-------+ X +-----|----^----+
| | / \ | |
| | / \ | |
+--v----|-------+ +--v-----\-+ +--v----|-+
| terminal | | PTY | | PTY |
| device driver | | master | | slave |
+------^--------+ +--|-----^-+ +--|----^-+
| | | v |
/----v----\ v +<------------------------+ |
| user at a | +----------------------------------->+
| terminal |
\---------/
这个例子,展示了pty程序-e选项的需要。pty程序并不是交互运行的,因为它的标准输入没有连接到终端上面。因为对isatty返回为false,前面"pty程序的main函数"里面,交互的标记默认设置为false。也就是说,实际终端上面的行规则部分保持一个回显被打开的canonical模式。通过指定-e选项,我们关闭了PTY slave上面的行规则模块的回显。如果我们没有做这个步骤,我们所键入的就会被两个行规则模块显示两次。
我们指定-e选项也会关闭termios结构中的ONLCR标记,以组织所有来自协作处理进程的输出被回车或者新行终止。
在不同的系统上面测试这个例子,也显示了另外一个我们在14章8节中描述readn和writen函数时候暗示的问题。即当文件描述符号引用一个不是普通磁盘文件的时候,通过read返回的数据的数量,会根据实现有所不同。这个使用pty的协作处理进程的例子在还是15章2节中的那个程序里,由于其程序管道中的read函数返回少于一行,所以对read函数跟踪得到的数据结果是无法预料的。解决的方法就是不使用那个程序,但是使用本书中15章练习题第5题中的修改过的程序,这个程序使用了标准输入输出库,并且为两个管道的标准输入输出流设置了行缓冲。通过这样做,fgets函数读取的次数如同请求获取一整个行。15章2节的那个程序中的while循环假设每次向协作处理程序发送一行会导致一行被返回。
网友评论