美文网首页LinuxLinux学习之路
APUE读书笔记-19伪终端(10)

APUE读书笔记-19伪终端(10)

作者: QuietHeart | 来源:发表于2020-10-07 17:38 被阅读0次

5、pty程序

写pty程序是因为这样就可以用"pty prog arg1 arg2"方式的键入取代"prog arg1 arg2"方式的键入。

当我们使用pty来执行另外一个程序的时候,那个程序会在它自己的session中被执行,并且连接到一个伪终端上面。下面,我们来看一下pty程序的源代码。

pty程序的main函数

首先是包含main函数的文件,这个文件会调用前面章节中定义的pty_fork函数。

#include "apue.h"
#include <termios.h>
#ifndef TIOCGWINSZ
#include <sys/ioctl.h>  /* for struct winsize */
#endif

#ifdef LINUX
#define OPTSTR "+d:einv"
#else
#define OPTSTR "d:einv"
#endif

static void set_noecho(int);    /* at the end of this file */
void        do_driver(char *);  /* in the file driver.c */
void        loop(int, int);     /* in the file loop.c */

int main(int argc, char *argv[])
{
    int             fdm, c, ignoreeof, interactive, noecho, verbose;
    pid_t           pid;
    char            *driver;
    char            slave_name[20];
    struct termios  orig_termios;
    struct winsize  size;

    interactive = isatty(STDIN_FILENO);
    ignoreeof = 0;
    noecho = 0;
    verbose = 0;
    driver = NULL;

    opterr = 0;     /* don't want getopt() writing to stderr */
    while ((c = getopt(argc, argv, OPTSTR)) != EOF) {
        switch (c) {
        case 'd':        /* driver for stdin/stdout */
            driver = optarg;
            break;
        case 'e':        /* noecho for slave pty's line discipline */
            noecho = 1;
            break;

        case 'i':       /* ignore EOF on standard input */
            ignoreeof = 1;
            break;

        case 'n':       /* not interactive */
            interactive = 0;
            break;

        case 'v':       /* verbose */
            verbose = 1;
            break;

        case '?':
            err_quit("unrecognized option: -%c", optopt);
        }
    }
    if (optind >= argc)
        err_quit("usage: pty [ -d driver -einv ] program [ arg ... ]");

    if (interactive) {  /* fetch current termios and window size */
        if (tcgetattr(STDIN_FILENO, &orig_termios) < 0)
            err_sys("tcgetattr error on stdin");
        if (ioctl(STDIN_FILENO, TIOCGWINSZ, (char *) &size) < 0)
            err_sys("TIOCGWINSZ error");
        pid = pty_fork(&fdm, slave_name, sizeof(slave_name),
          &orig_termios, &size);
    } else {
        pid = pty_fork(&fdm, slave_name, sizeof(slave_name),
          NULL, NULL);
    }

    if (pid < 0) {
        err_sys("fork error");
    } else if (pid == 0) {      /* child */
        if (noecho)
            set_noecho(STDIN_FILENO);   /* stdin is slave pty */

        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)  /* user's tty to raw mode */
        err_sys("tty_raw error");
    if (atexit(tty_atexit) < 0)         /* reset user's tty on exit */
        err_sys("atexit error");
    }

    if (driver)
        do_driver(driver);   /* changes our stdin/stdout */

    loop(fdm, ignoreeof);    /* copies stdin -> ptym, ptym -> stdout */

    exit(0);
}

static void set_noecho(int fd)     /* turn off echo (for slave pty) */
{
    struct termios stermios;

    if (tcgetattr(fd, &stermios) < 0)
        err_sys("tcgetattr error");

    stermios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);

    /*
     * Also turn off NL to CR/NL mapping on output.
     */
    stermios.c_oflag &= ~(ONLCR);

    if (tcsetattr(fd, TCSANOW, &stermios) < 0)
        err_sys("tcsetattr error");
}

在下一节,我们将要看到使用pty程序的各种不同的命令行选项。getopt函数帮助我们将命令参数以一个比较一致的方式包装起来,本书21章对getopt函数进行了更详细的解说。

注:鉴于以后可能不会再对像20,21章这样只是开发一个程序的过程描述、这样的章节进行详细的翻译,后面给出一个经过实践的使用和解说getopt的例子, 参见: 对于getopt函数的解说以及例子 (参见第5.3.1节)。

言归正转(早上上班的时候在公交车上学到的,用英语应该是"Let's get down to business."_),

  • 在调用pty_fork之前,我们获得当前termios和winsize结构的值,并且将它们作为pty_fork的参数进行传递。采用这个方法,PTY slave假设具有和当前终端同样的初始状态。
  • 从pty_fork返回之后,子进程可以关闭slave PTY的显示,然后调用execvp来执行命令行中指定的程序。所有剩下的命令行的参数都会被作为参数传递给这个程序。
  • 父进程可以设置用户终端为raw模式。在这个情况下,父进程也设置exit处理函数以便调用exit的时候重置终端状态。我们后面会对do_driver函数进行描述。
  • 父进程然后调用loop函数,这个函数会将从标准输入上面接收到的所有数据拷贝到PTY master上面,并且将拷贝PTY master上面的所有内容到标准输出。为了表示多样性,我们这里在两个进程中对它进行拷贝。当然,在一个进程中使用select,pool或者多线程的方式也可以做到这个效果。

相关文章

网友评论

    本文标题:APUE读书笔记-19伪终端(10)

    本文链接:https://www.haomeiwen.com/subject/yatluktx.html