分类: C/C++
2008-03-10 07:23:04
19.4 pty_fork函数
现在使用上一节介绍的两个函数: p t y m _ o p e n和p t y s _ o p e n,编写我们称之为p t y _ f o r k的函数。这个新函数具有如下功能:打开主设备和从设备,建立作为对话期管理者的子进程并使其具有控制终端。
# include
#include
#include
#include "ourhdr.h"
pid_t pty_fork (int * ptrfdm, char * slave_name,
const struct termios * slave_termios,
const struct winsize * slave_winsize) ;
返 回 : 子 进 程 中 为 0 , 父 进 程 中 为 子 进 程 的 进 程 ID,若 错 误 则 为 - 1
p t y主设备的文件描述符通过p t r f d m指针返回。
如果s l a v e _ n a m e不为空,从设备的名称被存放在该指针指向的存储区中。调用者必须为该存储区分配空间。如果指针s l a v e _ t e r m i o s不为空,该指针所引用的结构将初始化从设备的终端行规程。如果该指针为空,系统将从设备的t e r m i o s结构初始化为一个由具体应用定义的初始状态。类似的,如果s l a v e _ w i n s i z e指针不为空,该指针所引用的结构将初始化从设备的窗口大小。如果该指针为空,w i n s i z e结构通常被初始化为0。
程序1 9 - 3显示了这个程序的代码。调用相应的p t y m _ o p e n和p t y s _ o p e n函数,这个函数在S V R 4和4 . 3 + B S D系统下都可以使用。
在打开伪终端主设备后, f o r k将被调用。正如前面提到的,要等到调用setid建立新的对话期后才调用ptys_open。当调用setsid时,子进程还不是一个进程组的首进程(想一想为什么?)
因此9 . 5节列出的三个操作被使用:(a)子进程作为对话的管理者创建一个新的对话期;( b)子进程创建一个新的进程组;(c)子进程没有控制终端。在S V R 4系统中,当调用p t y s _ o p e n时,从设备成为了控制终端。在4 . 3 + B S D系统中,必须调用ioctl并使用参数T I O C S C T T Y来分配一个控制终端。然后termios和w i n s i z e这两个结构在子进程中被初始化。最后从设备的文件描述符被复制到子进程的标准输入、标准输出和标准出错中。这表示由子进程所e x e c的进程都会将上述三个句柄同伪终端从设备联系起来。在调用f o r k后,父进程返回伪终端主设备的描述符并返回。下一节将在p t y程序中使用pty _ fork。
程序19-3 pty_fork函数
#include
#include
#ifndef TIOCGWINSZ
#include
#endif
#include "ourhdr.h"
pid_t pty_fork (int *ptrfdm, char *slave_name, const struct termios *slave_termios, const struct winsize *slave_winsize)
{
int fdm, fds;
pid_t pid;
char pts_name[20];
if ( (fdm = ptym_open(pts_name)) <0)
err_sys("can't open master pty: %s ", pts_name);
if (slave_name != NULL)
strcpy(slave_name,pts_name);
if ( (pid = fork ()) < 0)
return (-1);
else if (pid == 0){
if (setsid() < 0)
err_sys("setsid error");
if ((fds = pty_open (fdm,pts_name)) < 0)
err_sys ("can't open slave pty");
close (fdm);
#if define(TIOCSTTY) && !define (CIBAUD)
if (ioctl (fds, TIOCSTTY, (char *) 0 ) < 0)
err_sys("TIOCSTTTY error");
#endif
if (slave_termios ! = NULL){
if (tcsetattr (fds, TCSANOW, slave_termios) < 0)
err_sys("tcsetattr error on slave pty");
}
if (slave_winsize != NULL){
if (iioctl (fds, TIOCSWINSZ, slave_winsize) < 0)
err_sys("TIOCSWINSZ error on slave pty");
}
if (dup2(fds, STDIN_FILENO) != STDIN_FILENO)
err_sys ("dup2 error on stdin");
if (dup2(fds, STDOUT_FILENO) != STDOUT_FILENO)
err_sys ("dup2 error on stdout");
if (dup2(fds, STDERR_FILENO) != STDERR_FILENO)
err_sys ("dup2 error on stderr");
if (fds > STDERR_FILENO)
close (fds);
return (0);
}
else {
*ptrfdm = fdm;
return (pid);
}
}
19.5 pty程序
编写p t y程序的目的是为了用键入:pty prog arg1 arg2 来代替:prog arg1 arg2, 这样使我们可以用p t y来执行另一个程序,该程序在一个自己的对话期中执行,并和一个
伪终端连接。
看一下p t y程序的源码。程序1 9 - 4包含m a i n函数。它调用上一节的p t y _ f o r k函数。
程序19-4 pty程序的m a i n函数
#include
#include
#ifndef TIOCGWINSZ
#include
#endif
#include "ourhdr.h"
static void set_noecho (int ); /*at the end of this file */
void do_driver (char *);
void loop(int , int);
int ain (int argc, char *argv[])
{
int fdm, c, ignoreeof, interactive, noecho, verbose;
pid_t pid;
char *driver, slave_name[20];
struct termios orig_termios;
struct winsize size;
interactive = isatty (STDIN_FILENO);
ignoreeof = 0;
noecho = 0;
verbose = 0;
driver = NULL;
opterr = 0;
while ((c = getopt (argc, argv, "d:einv")) != EOF) {
switch (c) {
case 'd':
driver =optarg;
break;
case 'e':
noecho = 1;
break;
case 'i':
ignoreeof = 1;
break;
case 'n':
interactive = '0';
break;
case 'v':
verbose = 1;
break;
case '?':
err_quit ("unrecognize option: -%c", optopt);
}
}
if (optind >= argc)
err_quit ("Usage: pty [-d drive -einv] progra [arg ...]");
if (interactive)
{
if (togetattr (STDIN_FILENO, &orig_termios) < 0)
err_sys ("tcgetattr error on stdin");
if (ioctl(STDIN_FILENO, TIOCGWINSZ, (char *) &size) < 0)
err_sys ("TIOCGWINDSZ error");
pid = pty_fork (&fdm, slave_name, &orig_termios, &size);
}
else
pid = pty_fork (&fdm, slave_name, NULL, NULL);
if (pid < 0)
err_sys ("fork error");
else if (pid == 0){
if (noecho)
set_noecho(STDIN_FILENO);
if (execvp(argv(optind), &argv[optind]) < 0)
err_sys("can't execute: %s ", argv[optind]);
}
if (verbose){
fprintf(stderr, "slave name = %s \n", slave_name);
if (driver != NULL)
fprintf(stderr, "driver = %s \n",driver);
}
if (interactive && driver == NULL) {
if (tty_raw (STDIN_FILENO) < 0)
err_sys("tty_raw error");
if (atexit(tty_atexit) < 0)
err_sys("atexit error");
}
if (driver )
do_driver (driver);
loop(fdm, ignoreeof);
exit (0);
}
static void set_noecho (int fd)
{
struct termios stermios;
if (tcgetattr(fd, &sterios) < 0)
err_sys("tcgetattr error");
stermios.c_lflag &= ~(ECHO | ECHOE |ECHOK |ECHONL);
stermios.c_oflag & = ~(ONLCR);
if (tcsetattr (fd, TCSANOW, &stermios) < 0)
err_sys("tcsetattr error");
}
下一节检测p t y程序的不同使用时,将会探讨多种的行命令选择项。
在调用p t y _ f o r k前,我们取得了t e r m i o s和w i n s i z e结构的值,将其传递给p t y _ f o r k。通过这种方法,伪终端从设备具有和现在的终端相同的初始状态。
从p t y _ f o r k返回后,子进程关闭了伪终端从设备的回显,并调用e x e c v p来执行命令行指定的程序。所有的命令行参数将成为程序的参数。
父进程在调用e x i t时执行原先设置的退出处理程序,它复原终端状态,将用户终端设置为
初始模式(可选)。下一节将讨论d o _ d r i v e r函数。
接下来父进程调用函数l o o p(见程序1 9 - 5)。该函数仅仅是将所有标准输入拷贝到伪终端主设备,并将伪终端主设备接收到的所有内容拷贝到标准输出。同1 8 . 7节一样,我们有两个选择—一个进程还是两个进程?为了有所区别,这里使用两个进程,尽管使用s e l e c t或p o l l的单进程也是可行的。
程序19-5 loop函数
#include
#include
#include "ourhdr.h"
#define BUFSIZE 512
static void sig_term (int);
static volatile sig_atomic_t sigcaught; /*set by signal handler */
void loop (int ptym, int igoreeof)
{
pid_t child;
int nread;
char buff[BUFSIZE];
if ((child = fork ()) < 0)
err_sys("fork error");
else if (child == 0){
`for (;;){
if ((nread = read (STDIN_FILENO, buff, BUFFSIZE)) < 0)
err_sys("read error from stdin");
else if (nread == 0)
break;
if (writen(ptym,buf,nread) != nread)
err_sys("writen error to aster pty");
}
if (ignoreeof == 0)
kill (getpid (), SIGTERM);
exit (0);
}
if (signal_intr(SIGTERM, sig_term) == SIG_ERR)
err_sys("signal_intr error for SIGTERM");
for (;;) {
if ((nread = read(ptym,buff,BUFFSIZE)) <= 0)
break;
if (writen (STDOUT_FILENO,buff, nread) != nread)
err_sys("writen error to stdout");
}
if (sigcaught == 0)
kill (child, SIGTERM);
return ;
}
static void sig_term(int signo)
{
sigcaught = 1;
return ;
}
注意,当使用两个进程时,如果一个终止,那么它必须通知另一个。我们用S I G T E R M进
行这种通知。