ptmx/pts

简介

ptmx,pts pseudo terminal master and slave
ptmx与pts配合实现pty(伪终端)
在telnet,ssh等远程终端工具中会使用到pty,通常的数据流是这样的
telnetd进程 ---> /dev/ptmx(master) ---> /dev/pts/?(slave) ---> getty
telnetd进程收到网络中的数据后,将数据丢给ptmx,ptmx像管道一样将数据丢给pts/?,getty进程从pts/?读取数据传递给shell去执行。

linux支持的两种pty
a. UNIX98 pseudoterminal,使用的是devpts文件系统,挂载在/dev /pts目录
b. 在UNIX98 pseudoterminal之前,master pseudoterminal名字为/dev/ptyp0,…,slave pseudoterminal名字为/dev/ttyp0,…,这个方法需要预先分配好很多的设备节点。

只有在open /dev/ptmx程序不退出的情况下,/dev/pts/目录下才会有对应的设备节点
在程序执行”open /dev/ptmx”的时候会在/dev/pts/目录下生成一个设备节点,比如0,1…,但是当程序退出的时候这个设备节点就消失了。可以通过如下一个例子演示在”open /dev/ptmx”的时候在/dev/pts目录下生成的设备节点

$ ls /dev/pts; ls /dev/pts </dev/ptmx
0  1  2  ptmx
0  1  2  3  ptmx

可见在重定向/dev/ptmx的时候在/dev/pts目录下多了个设备节点3,而当上面这个shell结束的时候再次ls /dev/pts目录,设备节点3又消失了。


程序

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pty.h>

int main()
{
        int fd_m, fd_s;
        int len;
        const char *pts_name;
        char send_buf[64] = "abc\ndefghijk\nlmn";
        char recv_buf[64] = {0};

        fd_m = open("/dev/ptmx", O_RDWR | O_NOCTTY);
        if (fd_m < 0) {
                printf("open /dev/ptmx fail\n");
                return -1;
        }

        if (grantpt(fd_m) < 0 || unlockpt(fd_m) < 0) {
                printf("grantpt and unlockpt fail\n");
                goto err;
        }

        pts_name = (const char *)ptsname(fd_m);
        fd_s = open(pts_name, O_RDONLY | O_NOCTTY);
        if (fd_s < 0) {
                printf("open /dev/ptmx fail\n");
                goto err;
        }

        len = write(fd_m, send_buf, strlen(send_buf));
        printf("write len=%d\n", len);

        len = read(fd_s, recv_buf, sizeof(recv_buf));
        printf("read len=%d, recv_buf=[%s]\n", len, recv_buf);

        len = read(fd_s, recv_buf, sizeof(recv_buf));
        printf("read len=%d, recv_buf=[%s]\n", len, recv_buf);

        close(fd_m);
        close(fd_s);
        return 0;

err:
        if (fd_m)
                close(fd_m);
        if (fd_s)
                close(fd_s);

        return -1;
}

上面这段程序的输出如下:
这里写图片描述
read只有遇到换行符’\n’的时候才会返回,否则遇不到的话一直阻塞在那里。

每open /dev/ptmx就会得到一个新的文件描述符,并且在/dev/pts/目录下生成一个与这个文件描述符对应的新的设备节点
当进程open “/dev/ptmx”的时候,获得了一个新的pseudoterminal master(PTM)的文件描述符,同时会在/dev/pts目录下自动生成一个新的pseudoterminal slave(PTS)设备。每次open “/dev/ptmx”会得到一个不同的PTM文件描述符(多次open会得到多个文件描述符),并且有和这个PTM描述符关联的PTS。

grantpt, unlockpt: 在每次打开pseudoterminal slave的时候,必须传递对应的PTM的文件描述符。grantpt以获得权限,然后调用unlockpt解锁
ptsname: 将PTM的文件描述符作为参数,会得到该描述符对应的PTS的路径

向PTM写的数据可以从PTS读出来,向PTS写的数据可以从PTM读出来。


原理

对ptmx执行open操作,将创建一对tty主从设备

tty_init
    cdev_init(&ptmx_cdev, &ptmx_fops);//创建了/dev/ptmx设备节点
    //此时/dev/ptmx设备节点的open函数为ptmx_fops.ptmx_open()

static int ptmx_open(struct inode *inode, struct file *filp)
{
    ...
    idr_ret = idr_get_new(&allocated_ptys, NULL, &index);
    ...
    //NR_UNIX98_PTY_DEFAULT也就是4096if (index >= pty_limit) {
        ...
    }
    ...
    mutex_lock(&tty_mutex);
    //以index为pts的设备索引号,创建成对的主从设备ptmx和pts
    retval = init_dev(ptm_driver, index, &tty);
    mutex_unlock(&tty_mutex);
    ...
    retval = ptm_driver->open(tty, filp);
    ...

所以在fd_m = open("/dev/ptmx", O_RDWR)操作之后,将产生一个成对的ptmx和pts主从pty设备,并返回ptmx对应的文件描述符。

调用ioctl获得与ptmx对应的pts的设备节点

tty_ioctl
    tty->driver->ioctl
    //在unix98_pty_init()中,仅对ptmx主设备赋予了ioctl操作,`ptm_driver->ioctl =pty_unix98_ioctl;`
    pty_unix98_ioctl

static int pty_unix98_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)
{
    switch (cmd) {
    case TIOCSPTLCK: /* Set PT Lock (disallow slave open) */
        return pty_set_lock(tty, (int __user *)arg);
    case TIOCGPTN: /* Get PT Number */
        //当前tty对应的为ptmx结构,它的index就是与之配对的pts
        return put_user(tty->index, (unsigned int __user *)arg);
    }

    return -ENOIOCTLCMD;
}

看看glibc库中如何封装ptsname函数

char* ptsname( int fd )
{
    unsigned int pty_num;
    static char buff[64];

    //最终调用上面的pty_unix98_ioctl获取当前ptmx主设备对应的pty从设备号.
    if ( ioctl( fd, TIOCGPTN, &pty_num ) != 0 )
        return NULL;

    //格式化为/dev/pts/0,/dev/pts/1等,即:pts对应的文件全路径.
    snprintf( buff, sizeof(buff), "/dev/pts/%u", pty_num );
    return buff;
}

adb中遇到的场景

我在移植adb到linux平台的时候,涉及到pts、ptmx,需要在内核中配置如下:

Device driver`
    Character devices
        [*]Enable TTY
            [*]Unix98 PTY support
                [*]Support multiple instances of devpts
Config busybox setting
    Busybox Setting
        General Configuration
            Use the devpts filesystem for unix98 PTYs

选项选择好编译,再次去除这些选项之后再编译会出问题,报如下错误:

    WARNING: arch/rlx/bsp/built-in.o(.text+0x4): Section mismatch in reference from the function >disable_early_printk() to the variable .init.data:promcons_output
    The function disable_early_printk() references
    the variable __initdata promcons_output.
    This is often because disable_early_printk lacks a __initdata
    annotation or the annotation of promcons_output is wrong.

    WARNING: arch/rlx/bsp/built-in.o(.text+0x10): Section mismatch in reference from the function >disable_early_printk() to the variable .init.data:promcons_output
    The function disable_early_printk() references
    the variable __initdata promcons_output.
    This is often because disable_early_printk lacks a __initdata
    annotation or the annotation of promcons_output is wrong.

    WARNING: arch/rlx/bsp/built-in.o(.data+0x1710): Section mismatch in reference from the variable >rts1_camera_device to the variable .init.rodata:rts1_soc_camera_pdata
    The variable rts1_camera_device references
    the variable __initconst rts1_soc_camera_pdata
    If the reference is valid then annotate the
    variable with __init* or __refdata (see linux/init.h) or name the variable:
    _template, _timer, _sht, _ops, _probe, _probe_one, *_console

解决办法:
不用去修改这些文件的错误,虽然这些文件本身编译可能有问题。
可以这么做,在busybox和linux内核目录下做make clean和make distclean
然后再重新编译。
实际上后来发现,busybox中不需要配置,只要在内核中配置就行了。

内核配置并且编译之后,系统支持pts
进入终端执行:mount -t devpts none /dev/pts
如果没有/dev/pts这个目录的话,执行mkdir -p /dev/pts创建一个目录


参考文章

  1. ptmx(4) - Linux man page
  2. Linux中的伪终端编程
  3. 浅析terminal创建时ptmx和pts关系
  4. linux-3.14/Documentation/filesystems/devpts.txt
  5. 5.
©️2020 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值