📄 19.htm
字号:
<p>erbose; </p>
<p>pid_t pid; </p>
<p>char *driver, slave_name[20]; </p>
<p>struct termios orig_termios; </p>
<p>struct winsize size; </p>
<p>interactive = isatty(STDIN_FILENO); </p>
<p>ignoreeof = 0; </p>
<p>noecho = 0; </p>
<p>verbose = 0; </p>
<p>driver = NULL; </p>
<p>opterr = 0; /* don't want getopt() writing to stderr */ </p>
<p>while ( (c = getopt(argc, argv, "d:einv")) != EOF) { </p>
<p>switch (c) { </p>
<p>case 'd': /* driver for stdin/stdout */ </p>
<p>driver = optarg; </p>
<p>break; </p>
<p>case 'e': /* noecho for slave pty's line disciplin </p>
<p>*/ </p>
<p>noecho = 1; </p>
<p>break; </p>
<p>case 'i': /* ignore EOF on standard input */ </p>
<p>ignoreeof = 1; </p>
<p>break; </p>
<p>case 'n': /* not interactive */ </p>
<p>interactive = 0; </p>
<p>break; </p>
<p>case 'v': /* verbose */ </p>
<p>verbose = 1; </p>
<p>break; </p>
<p>case '?': </p>
<p>err_quit("unrecognized option: -%c", optopt); </p>
<p>} </p>
<p>} </p>
<p>if (optind >= argc) </p>
<p>err_quit("usage: pty [ -d driver -einv ] program [ arg ...</p>
<p>]"); </p>
<p>if (interactive) { /* fetch current termios and window size</p>
<p>*/ </p>
<p>if (tcgetattr(STDIN_FILENO, &orig_termios) < 0) </p>
<p>err_sys("tcgetattr error on stdin"); </p>
<p>if (ioctl(STDIN_FILENO, TIOCGWINSZ, (char *) &size) < 0) </p>
<p>err_sys("TIOCGWINSZ error"); </p>
<p>pid = pty_fork(&fdm, slave_name, &orig_termios, &size); </p>
<p>} else </p>
<p>pid = pty_fork(&fdm, slave_name, NULL, NULL); </p>
<p>if (pid < 0) </p>
<p>err_sys("fork error"); </p>
<p>else if (pid == 0) { /* child */ </p>
<p>if (noecho) </p>
<p>set_noecho(STDIN_FILENO); /* stdin is slave pty */ </p>
<p>if (execvp(argv[optind], &argv[optind]) < 0) </p>
<p>err_sys("can't execute: %s", argv[optind]); </p>
<p>} </p>
<p>if (verbose) { </p>
<p>err_quit("unrecognized option: -%c", optopt); </p>
<p>} </p>
<p>} </p>
<p>if (optind >= argc) </p>
<p>err_quit("usage: pty [ -d driver -einv ] program [ arg ...</p>
<p>]"); </p>
<p>if (interactive) { /* fetch current termios and window size</p>
<p>*/ </p>
<p>if (tcgetattr(STDIN_FILENO, &orig_termios) < 0) </p>
<p>err_sys("tcgetattr error on stdin"); </p>
<p>if (ioctl(STDIN_FILENO, TIOCGWINSZ, (char *) &size) < 0) </p>
<p>err_sys("TIOCGWINSZ error"); </p>
<p>pid = pty_fork(&fdm, slave_name, &orig_termios, &size); </p>
<p>} else </p>
<p>pid = pty_fork(&fdm, slave_name, NULL, NULL); </p>
<p>if (pid < 0) </p>
<p>err_sys("fork error"); </p>
<p>else if (pid == 0) { /* child */ </p>
<p>if (noecho) </p>
<p>set_noecho(STDIN_FILENO); /* stdin is slave pty */ </p>
<p>if (execvp(argv[optind], &argv[optind]) < 0) </p>
<p>err_sys("can't execute: %s", argv[optind]); </p>
<p>} </p>
<p>if (verbose) { </p>
<p>stermios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); </p>
<p>stermios.c_oflag &= ~(ONLCR); </p>
<p>/* also turn off NL to CR/NL mapping on output */ </p>
<p>if (tcsetattr(fd, TCSANOW, &stermios) < 0) </p>
<p>err_sys("tcsetattr error"); </p>
<p>} </p>
<p>_______________________________________________________________________</p>
<p>________ </p>
<p>程序19.4 pty程序的main函数 </p>
<p>在下一节我们检测pty程序的不同使用时,将会探讨不同的行命令选项。
</p>
<p>在调用pty_fork前,我们取得了termios和winsize结构的值,将其传递给</p>
<p>pty_fo </p>
<p>rk。通过这种方法,伪终端从设备具有和现在的终端相同的初始状态。
</p>
<p>从pty_fork返回后,子进程关闭了伪终端从设备的回显,并调用execvp来执</p>
<p>行命 </p>
<p>令行指定的程序。所有的命令行选项将成为程序的选项。 </p>
<p>父进程在调用exit时执行原先设置的exit处理程序,它复原终端状态,将用</p>
<p>户终 </p>
<p>端设置为初始模式(可选)。我们将在下一节讨论do_driver函数。 </p>
<p>接下来父进程调用函数loop(程序19.5)。该函数仅仅是将所有标准输入拷</p>
<p>贝到 </p>
<p>伪终端主设备,并将伪终端主设备收到的所有内容拷贝到标准输出。同18.7</p>
<p>节一样 </p>
<p>,我们有两个选择--一个进程还是两个?为了有所区别,我们在这里使用两</p>
<p>个进程 </p>
<p>,尽管使用select或poll的单进程也是可行的。 </p>
<p>_______________________________________________________________________</p>
<p>________ </p>
<p>#include <sys/types.h> </p>
<p>#include <signal.h> </p>
<p>#include "ourhdr.h" </p>
<p>#define BUFFSIZE 512 </p>
<p>static void sig_term(int); </p>
<p>static volatile sig_atomic_t sigcaught; /* set by signal</p>
<p>handler */ </p>
<p>void </p>
<p>loop(int ptym, int ignoreeof) </p>
<p>{ </p>
<p>pid_t child; </p>
<p>int nread; </p>
<p>char buff[BUFFSIZE]; </p>
<p>if ( (child = fork()) < 0) { </p>
<p>err_sys("fork error"); </p>
<p>} else if (child == 0) { /* child copies stdin to ptym */ </p>
<p>for ( ; ; ) { </p>
<p>if ( (nread = read(STDIN_FILENO, buff, BUFFSIZE)) < 0) </p>
<p>err_sys("read error from stdin"); </p>
<p>else if (nread == 0) </p>
<p>break; /* EOF on stdin means we're done </p>
<p>*/ </p>
<p>if (writen(ptym, buff, nread) != nread) </p>
<p>err_sys("writen error to master pty"); </p>
<p>} </p>
<p>/* We always terminate when we encounter an EOF on stdin </p>
<p>but we only notify the parent if ignoreeof is 0. */ </p>
<p>if (ignoreeof == 0) </p>
<p>kill(getppid(), SIGTERM); /* notify parent */ </p>
<p>exit(0); /* and terminate; child can't return */ </p>
<p>} </p>
<p>/* parent copies ptym to stdout */ </p>
<p>if (signal_intr(SIGTERM, sig_term) == SIG_ERR) </p>
<p>err_sys("signal_intr error for SIGTERM"); </p>
<p>for ( ; ; ) { </p>
<p>if ( (nread = read(ptym, buff, BUFFSIZE)) <= 0) </p>
<p>break; /* signal caught, error, or EOF */ </p>
<p>if (writen(STDOUT_FILENO, buff, nread) != nread) </p>
<p>err_sys("writen error to stdout"); </p>
<p>} </p>
<p>/* There are three ways to get here: sig_term() below caught</p>
<p>the </p>
<p>* SIGTERM from the child, we read an EOF on the pty master</p>
<p>(which </p>
<p>* means we have to signal the child to stop), or an error.</p>
<p>*/ </p>
<p>if (sigcaught == 0) /* tell child if it didn't send us the</p>
<p>signal */ </p>
<p>kill(child, SIGTERM); </p>
<p>return; /* parent returns to caller */ </p>
<p>} </p>
<p>/* The child sends us a SIGTERM when it receives an EOF on </p>
<p>* the pty slave or encounters a read() error. */ </p>
<p>static void </p>
<p>sig_term(int signo) </p>
<p>{ </p>
<p>sigcaught = 1; /* just set flag and return */ </p>
<p>return; /* probably interrupts read() of ptym */ </p>
<p>} </p>
<p>_______________________________________________________________________</p>
<p>________ </p>
<p>程序19.5 loop函数 </p>
<p>注意,当使用两个进程时,如果一个终止,那么它必须通知另一个。我们用</p>
<p>SIGT </p>
<p>ERM进行这种通知。 </p>
<p>19.6 使用pty程序 </p>
<p>接下来我们看一下pty程序的不同例子,了解一下使用不同命令行选项的必要</p>
<p>性。 </p>
<p>如果使用KornShell,我们执行: </p>
<p>pty ksh </p>
<p>得到一个运行在一个伪终端下的新的shell。 </p>
<p>如果文件ttyname同我们在程序11.7看到的程序相同,我们可按如下方式执</p>
<p>行pty </p>
<p>程序: </p>
<p>$who </p>
<p>stevens console Feb 6 10:43 </p>
<p>stevens ttyp0 Feb 6 15:00 </p>
<p>stevens ttyp1 Feb 6 15:00 </p>
<p>stevens ttyp2 Feb 6 15:00 </p>
<p>stevens ttyp3 Feb 6 15:48 </p>
<p>stevens ttyp4 Feb 7 14:28 ttyp4是正在使用的最高终? </p>
<p>设备 </p>
<p>$pty ttyname 在pty上运行程序1 </p>
<p>..7 </p>
<p>fd 0: /dev/ttyp5 ttyp5是下一个有? </p>
<p>的pty设备号 </p>
<p>fd 1: /dev/ttyp5 </p>
<p>fd 2: /dev/ttyp5 </p>
<p>utmp文件 </p>
<p>在6.7节,我们讨论了记录当前Unix系统登录用户的utmp文件。那么在伪终</p>
<p>端上运 </p>
<p>行程序的用户是否被认为登录了呢?如果是远程登录,telnetd和rlogind,</p>
<p>显然伪 </p>
<p>终端上的用户应该在utmp中拥有相应条目。但是,从窗口系统或script程序</p>
<p>运行的 </p>
<p>,在伪终端上运行shell的用户是否应该在utmp中拥有相应条目呢?这个问</p>
<p>题一直 </p>
<p>没有一个统一的认识。有的系统有记录,有的没有。如果没有记录的话,who</p>
<p>(1) </p>
<p>程序一般不会显示正在被使用的伪终端。 </p>
<p>除非utmp允许其他用户的写权限,否则一般的程序将不能对其进行写操作。</p>
<p>但是, </p>
<p>一些系统提供这个写权限。 </p>
<p>作业控制交互 </p>
<p>当我们在pty上运行作业控制shell时,它能够正常地运行。例如,在pty上</p>
<p>运行Ko </p>
<p>rnShell。我们能够在这个新shell下运行程序和使用作业控制,如同在登录</p>
<p>shell </p>
<p>中一样。但如果在pty下我们运行一个交互式程序而不是作业控制shell,比</p>
<p>如: </p>
<p>pty cat </p>
<p>一切正常直到我们键入作业控制的暂停字符。在SVR4和4.3+BSD系统中作业</p>
<p>控制暂 </p>
<p>停字符将会被显示为^Z而被忽略。在SunOS4.1.2中,cat进程终止,pty进</p>
<p>程终止, </p>
<p>我们回到初始登录shell。 </p>
<p>为了明白其中的原因,我们需要检查所有相关的进程、这些进程所属的进程组</p>
<p>和 </p>
<p>会话。图19.7显示了pty cat运行的结构图。 </p>
<p>图19.7 pty cat的进程组和会话 </p>
<p>当我们键入暂停字符(Control-Z),它将被cat进程下的行规程模块所识</p>
<p>别,这是 </p>
<p>因为pty将终端(在pty parent之下)设置为初始模式。但内核不会终止cat</p>
<p>进程, </p>
<p>这是因为它属于一个孤儿进程组(9.10节)。cat的父进程是pty的父进程,</p>
<p>属于另 </p>
<p>一个会话。 </p>
<p>不同的系统处理这个情况的方法也不同。在POSIX.1中这个SIGTSTP信号不被</p>
<p>发送 </p>
<p>给进程。早期的Berkley系统发送一个进程甚至不能捕获的SIGKILL。这就是</p>
<p>我们在 </p>
<p>SunOS4.1.2中看到的。(POSIX.1 Rationale建议用SIGHUP作为更好的替</p>
<p>代,因为 </p>
<p>进程能够捕获它。)用程序8.17查看cat进程的终止状态,可以发现该进程确</p>
<p>实由 </p>
<p>SIGKILL信号终止。 </p>
<p>在SVR4和4.3+BSD系统中,我们修改了程序10.22来观察结果。修改后的程序</p>
<p>能够 </p>
<p>在捕获SIGTSTP后进行打印,并在捕获SIGCONT信号后再打印一次并继续执</p>
<p>行。这说 </p>
<p>明SIGTSTP被进程捕获,但是当进程试图发送信号给自己来暂停本进程的时</p>
<p>候,内 </p>
<p>核立即发送SIGCONT信号使之继续执行。内核将不会让进程被作业控制停止。</p>
<p>SVR4 </p>
<p>和4.3+BSD系统的这种处理方法比起发送一个SIGKILL的方法来显得不那么激</p>
<p>烈。 </p>
<p>当我们使用pty来运行作业控制shell时,被这个新shell调用的作业将不是</p>
<p>任何孤 </p>
<p>儿进程组的成员,这是因为作业控制shell总是属于同一个会话。在这种情况</p>
<p>下, </p>
<p>Control-Z被发送到被shell调用的进程,而不是shell本身。 </p>
<p>唯一的让被pty调用的进程能够处理作业处理信号的方法是:另外增加一个命</p>
<p>令行 </p>
<p>标志让pty子进程能够自己认识作业暂停字符,而不是让该字符通过其他行规</p>
<p>程模 </p>
<p>块。 </p>
<p>检查长时间运行程序的输出 </p>
<p>另一个使用pty进行作业控制交互的例子是图19.6。如果我们运行一个程序:</p>
<p>pty slowout > file.out & </p>
<p>当子进程试图从标准输入(终端)读入数据时,pty进程立刻停止运行。这是</p>
<p>因为 </p>
<p>该作业是一个后台作业并且当它试图访问终端时会使作业控制停止。如果我们</p>
<p>将标 </p>
<p>准输入重定向使得pty不从终端读取数据,如: </p>
<p>pty slowout < /dev/null > file.out & </p>
<p>那么pty程序立即终止,这是因为它从标准输入读取到一个文件结束符。解决</p>
<p>这个 </p>
<p>问题的方法是使用-i选项。这个选项的含义是忽略来自标准输入的文件结束</p>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -