伪终端pty的原理及使用

1. what is pseudo termina

伪终端(Pseudo Terminal)是成对的逻辑终端设备(即master和slave设备, 对master的操作会反映到slave上)。
例 如/dev/ptyp3和/dev/ttyp3(或者在设备文件系统中分别是/dev/pty/m3和 /dev/pty/s3)。它们与实际物理设备并不直接相关。如果一个程序把ptyp3(master设备)看作是一个串行端口设备,则它对该端口的读/ 写操作会反映在该逻辑终端设备对应的另一个ttyp3(slave设备)上面。而ttyp3则是另一个程序用于读写操作的逻辑设备。

这 样,两个程序就可以通过这种逻辑设备进行互相交流,而其中一个使用ttyp3的程序则认为自己正在与一个串行端口进行通信。这很象是逻辑设备对之间的管道 操作。对于ttyp3(s3),任何设计成使用一个串行端口设备的程序都可以使用该逻辑设备。但对于使用ptyp3的程序,则需要专门设计来使用 ptyp3(m3)逻辑设备。

/***************************************/
支持两种类型的pty:
CONFIG_UNIX98_PTYS=y
CONFIG_LEGACY_PTYS=y
CONFIG_LEGACY_PTY_COUNT=16

/***************************************/

2. How to implement pty in kernel

drivers/tty/pty.c

module_init(pty_init);

static int __init pty_init(void)
{
    legacy_pty_init();
    unix98_pty_init();
    return 0;
}
这里只关注legacy pty,两者本质上是一样的,只是细节上不同而已:

/***************************************/

2.1 legacy_pty_init

static void __init legacy_pty_init(void)
{
    struct tty_driver *pty_driver, *pty_slave_driver;

1]有关全局变量legacy_count

static int legacy_count = CONFIG_LEGACY_PTY_COUNT;

    if (legacy_count <= 0)
        return;

2]分配一对tty_driver: pty_deiver and pty_slave_derver

  并初始化,注意其中的other成员,两者的tty_operations基本相同,
  只是master多了一个ioctl函数
/*
static const struct tty_operations master_pty_ops_bsd = {
    .install = pty_install,
    .open = pty_open,
    .close = pty_close,
    .write = pty_write,
    .write_room = pty_write_room,
    .flush_buffer = pty_flush_buffer,
    .chars_in_buffer = pty_chars_in_buffer,
    .unthrottle = pty_unthrottle,
    .set_termios = pty_set_termios,
    .ioctl = pty_bsd_ioctl,
    .resize = pty_resize
};

static const struct tty_operations slave_pty_ops_bsd = {
    .install = pty_install,
    .open = pty_open,
    .close = pty_close,
    .write = pty_write,
    .write_room = pty_write_room,
    .flush_buffer = pty_flush_buffer,
    .chars_in_buffer = pty_chars_in_buffer,
    .unthrottle = pty_unthrottle,
    .set_termios = pty_set_termios,
    .resize = pty_resize
};
*/

   

   pty_driver = alloc_tty_driver(legacy_count);

    if (!pty_driver)
        panic("Couldn't allocate pty driver");

    pty_slave_driver = alloc_tty_driver(legacy_count);
    if (!pty_slave_driver)
        panic("Couldn't allocate pty slave driver");

    pty_driver->driver_name = "pty_master";
    pty_driver->name = "pty";
    pty_driver->major = PTY_MASTER_MAJOR;
    pty_driver->minor_start = 0;
    pty_driver->type = TTY_DRIVER_TYPE_PTY;
    pty_driver->subtype = PTY_TYPE_MASTER;
    pty_driver->init_termios = tty_std_termios;
    pty_driver->init_termios.c_iflag = 0;
    pty_driver->init_termios.c_oflag = 0;
    pty_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
    pty_driver->init_termios.c_lflag = 0;
    pty_driver->init_termios.c_ispeed = 38400;
    pty_driver->init_termios.c_ospeed = 38400;
    pty_driver->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW;
    pty_driver->other = pty_slave_driver;
    tty_set_operations(pty_driver, &master_pty_ops_bsd);

    pty_slave_driver->driver_name = "pty_slave";
    pty_slave_driver->name = "ttyp";
    pty_slave_driver->major = PTY_SLAVE_MAJOR;
    pty_slave_driver->minor_start = 0;
    pty_slave_driver->type = TTY_DRIVER_TYPE_PTY;
    pty_slave_driver->subtype = PTY_TYPE_SLAVE;
    pty_slave_driver->init_termios = tty_std_termios;
    pty_slave_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
    pty_slave_driver->init_termios.c_ispeed = 38400;
    pty_slave_driver->init_termios.c_ospeed = 38400;
    pty_slave_driver->flags = TTY_DRIVER_RESET_TERMIOS |
                    TTY_DRIVER_REAL_RAW;
    pty_slave_driver->other = pty_driver;
    tty_set_operations(pty_slave_driver, &slave_pty_ops_bsd);

3]注册具体的tty_driver到tty子系统

  根据flags,这里创建了dev/pty* and dev/ttyp*
    if (tty_register_driver(pty_driver))
        panic("Couldn't register pty driver");
    if (tty_register_driver(pty_slave_driver))
        panic("Couldn't register pty slave driver");
}

2.2 pty_install

/*当open 设备文件时,调用函数pty_install,该函数创建了成对的tty_struct*/
static int pty_install(struct tty_driver *driver, struct tty_struct *tty)
{
    struct tty_struct *o_tty;
    int idx = tty->index;
    int retval;

    o_tty = alloc_tty_struct();

    initialize_tty_struct(o_tty, driver->other, idx);

    /* We always use new tty termios data so we can do this
       the easy way .. */
    retval = tty_init_termios(tty);

    retval = tty_init_termios(o_tty);


    /*
     * Everything allocated ... set up the o_tty structure.
     */
    driver->other->ttys[idx] = o_tty;
    tty_driver_kref_get(driver->other);
    if (driver->subtype == PTY_TYPE_MASTER)
        o_tty->count++;
    /* Establish the links in both directions */
    tty->link   = o_tty;
    o_tty->link = tty;

    tty_driver_kref_get(driver);
    tty->count++;
    driver->ttys[idx] = tty;
    return 0;
}


2.3 pty 设备的写操作:写数据到虚拟设备对的另一端

/*pty 设备的写操作:写数据到虚拟设备对的另一端*/
/**
 *    pty_write        -    write to a pty
 *    @tty: the tty we write from
 *    @buf: kernel buffer of data
 *    @count: bytes to write
 *
 *    Our "hardware" write method. Data is coming from the ldisc which
 *    may be in a non sleeping state. We simply throw this at the other
 *    end of the link as if we were an IRQ handler receiving stuff for
 *    the other side of the pty/tty pair.
 */

static int pty_write(struct tty_struct *tty, const unsigned char *buf, int c)
{
    struct tty_struct *to = tty->link;

    if (tty->stopped)
        return 0;

    if (c > 0) {
        /* Stuff the data into the input queue of the other end */
        c = tty_insert_flip_string(to, buf, c);
        /* And shovel */
        if (c) {
            tty_flip_buffer_push(to);
            tty_wakeup(tty);
        }
    }
    return c;
}

2.4 pty 设备的读操作:虚拟设备的另一端会写数据到 readbuffe

/*pty 设备的读操作:虚拟设备的另一端会写数据到 readbuffer*/
也就是说函数tty_insert_flip_string等被调用


3 使用虚拟串口的例子:

3.1] 打开master side 端口:cat ptypb &

注意打开 slave side port 会失败,为什么请查一下?

3.1.1  如果先打开slave port的例子

root@android:/dev # cat ttyp6
pty_open: should open master first, otherwise error
/system/bin/sh: cat: ttyp6: I/O error
root@android:/dev # cat ptyp6

3.1.2 为什么先打开slave 出错

pty_install -> /*如果打开pty设备的类型是 master,则 count++*/
    if (driver->subtype == PTY_TYPE_MASTER)
        o_tty->count++;

pty_open -> /*如果pty对的另一方 count != 1, 则 return error*/
    if (tty->link->count != 1){
      pr_err("pty_open: should open master first, otherwise error\n");
      goto out;
    }                                

3.2] 打开slave side 端口cat > ttypb

root@android:/dev # cat > ttypb

3.3] 输入:

在ttypb设备上输入,在设备ptypb就读出相同的内容
qqq
qqq
qqq
qqq
111111
111111
4444
4444

3.4]另外使用echo "xxxx" > ptypb

虽然写入设备,但是写完后就关闭了设备,不能使用这种方法,只能使用cat


你可能感兴趣的:(tty,driver)