SVR4/4.3BSD与Linux对待伪终端的不同方式

简介:
打开伪终端意味着打开了一个“终端对”,这个终端对的其中一个是主终端,另一个是从终端,简单说主终端和类似sshd,telnetd等用户空间的远程协议处理进程连接,而从终端则和shell之类的实际进程连接,在处理远程登录的时候,一般都是由远程协议处理进程打开主终端和从终端,然后就在远程网络终端和本机shell之间建立了一条双向通道--“远程网络终端-(套接字)--本机协议处理进程--主终端--从终端--shell”,在这个“打开主从终端建立连接”的语义以及其实现上,有着不同的标准,总的来说有三种方式,分别是SVR4的方式,BSD的方式以及linux的方式,在“建立连接”的语义上SVR4的方式使用“流”来建立这条连接,而BSD和linux则是自动建立的,在“打开主从终端”的语义上,SVR4和linux是自动确定主终端并打开主终端后自动确定从终端,而BSD则必须手工确定和打开主终端,可见linux处理伪终端的方式是结合SVR4和BSD两种UNIX标准的结果,linux不仅实现这种有意义的最佳组合,而且分别实现了SRV和BSD的两种方式的接口,如果编译CONFIG_LEGACY_PTYS宏,则可以使用BSD的方式,如果编译CONFIG_UNIX98_PTYS,则实现SRV4的接口。
     参见《unix环境高级编程》的第19章,在用户空间,SVR4的方式为:
int ptym_open(char *pts_name)
{
    strcpy(pts_name, "/dev/ptmx"); //准备主终端的文件名字
    fdm = open(pts_name, O_RDWR);  //打开主终端
    grantpt(fdm);  //连接次终端
    ptr = ptsname(fdm); //得到次终端的文件名字
    strcpy(pts_name, ptr);
    return fdm;
}
int ptys_open(int fdm, char *pts_name)
{
    fds = open(pts_name, O_RDWR); //打开次终端
    ioctl(fds, I_PUSH, "ptem");   //压入一个伪终端虚拟模块
    ioctl(fds, I_PUSH, "ldterm"); //压入行规程模块
    return fds;
}
而BSD的方式却是:
int ptym_open(char *pts_name)
{
    ...
    //一个for循环,从/dev/ptyp0开始一直找到/dev/ptyTf为止,寻到第一个没有被使用的作为主终端,然后将对应的文件名的ptyXY中的p改为t,即ttyXY就是次终端
    pts_name[5] = 't';  ///dev/ptyXY中的第6个元素就是p,现在改为t
    return fdm;
}
int ptys_open(int fdm, char *pts_name)
{
    fds = open(pts_name, O_RDWR); //打开次终端
    //无需压入流模块,因为对于bsd来讲,其驱动程序是基于硬编码和clist的。
}
可见SRV4和BSD的方式根本不同,通过理解这种不同,我们也能更加明白/dev目录下的关于终端文件的命名规则了。关于流机制,BSD没有实现,可能是由于BSD自最初就没有SRV经过良好的规划吧,加之流的作者直接贡献流机制于SRV,那时unix已经分裂了。从终端的行规程以及任何终端的行规程之类其实也是一种协议,只是该协议主要规定人-机界面的规则,之所以将之称为行规程就是因为该规程在标准模式下限制了一次输入的结束就是一个换行符,这就是一个行规成协议,毕竟机器并不知道何时人们会输入完毕也就不能预先读取特定大小的数据块,而只能硬性规定一个特殊的字符作为输入结束,该特殊字符就是换行,类似tcp/ip协议,只是没有后者普遍罢了,使用压入流的方式,你可以轻易的堆积一个协议栈,只要将/dev/ip|udp|tcp等协议设备文件依次用ioctl的I_PUSH命令堆积即可。行规程压入了从终端并没有压入主终端,可见主终端仅仅起到一个数据中转的作用,主终端之所以不需要行规程,那是因为从终端可以处理直接从进程写入的数据或者说可以处理数据边界以及转义问题,这是无关紧要的,完全可以重新实现一个伪终端驱动,然后在主从终端都压入行规程模块,这样就显得更加对称了,正如linux后来实现的那样,虽然linux并没有显式地实现流机制,在linux中的伪终端tty的write函数中,主从终端都是统一一致的,如此一来在linux中,主从终端就更加像一对管道了,起码比SRV的要对称:
static int pty_write(...)
{
    struct tty_struct *to = tty->link; //直接获取“一对的另一半”
    ...
    to->ldisc.receive_buf(to, temp_buffer, NULL, n);//将数据放入另一半的缓冲区
    ...
}
如果看一下linux实现pty的源码,就会发现实际上pts使用的行规程是tty_ldisc_N_TTY,其receive_buf对数据其实并没有做太复杂的加工,因此这条管道并不复杂。
     在打开一对终端方面,linux实现了两种方式,SRV4的方式和BSD的方式,总之,实现这两种接口是之前unix标准混战的结果。以SRV4的方式为例,linux中使用了一个/dev/ptmx设备文件,该设备文件只有一个却可以集中代表所有的主终端,任何sshd,telnetd之类的进程都可以只使用者一个终端设备文件,虽然设备文件是一个,但是由于内核中file数据结构是基于进程的,因此各个进程对该设备文件的引用却可以容纳不同的数据,包括不同的从终端。在ptmx_open中,不仅系统可以自动分配一个主终端,而且还为该主终端绑定了一个从终端,主终端设置到file结构体的private_data字段上,之后诸如sshd,telnetd之类的进程读写/dev/ptmx文件时,虽然它们读写的是同一个文件,可是由于file结构体不再它们之间共享,因此它们取到的file->private_data也就不同了:
static int ptmx_open(struct inode * inode, struct file * filp)
{
    struct tty_struct *tty;
    int index;
    idr_pre_get(&allocated_ptys, GFP_KERNEL); //和BSD的实现不同,SRV的方式在内核中查找可用的主终端
    idr_ret = idr_get_new(&allocated_ptys, NULL, &index); //得到新的项
    ...
    retval = init_dev(ptm_driver, index, &tty);//此中实现终端对的相互link
    filp->private_data = tty; //重要的赋值
    devpts_pty_new(tty->link); //linux中的从终端设备文件是动态生成和删除的,因此linux使用了其强大的VFS机制,通过实现一个pts文件系统来支持这种动态的增删。
    ...
}
SRV4使用伪终端的方式是一对多的,ptmx集合了所有的主终端,同一个文件在不同的进程空间做区分,而BSD的方式却直接将多个一对一的终端对开放给接口调用者。在init_dev中,最为重要的一段是:
tty = alloc_tty_struct(); //分配主终端
initialize_tty_struct(tty); //初始化主终端的线路规程之类,包括些许工作队列
tty->driver = driver; //指定driver
tty->index = idx;
...
o_tty = alloc_tty_struct(); //分配从终端
initialize_tty_struct(o_tty); //初始化主终端的线路规程之类
o_tty->driver = driver->other; //pty_init的时候,主从driver都将other设置为对方
o_tty->index = idx;
tty_line_name(driver->other, idx, o_tty->name);
...
tty->link   = o_tty; //连接彼此,从此以后两个终端一主一从构成一个包含很多控制功能的管道
o_tty->link = tty;
linux的sshd等进程在调用完/dev/ptmx的open之后,实际上一对终端就建立起来了,由于没有实现SRV4的流机制,因此不需要在用ioctl将更多的协议模块PUSH上去了,这个过程直接在init_dev中就做了,可以猜想一切基于SRV4标准实现的OS,其ptmx的open要简单的多,因此init_dev简单得多,不需要系统做任何事,后续的所有工作几乎全靠调用者来用ioctl完成,比linux更灵活,但是对于很多凡人来讲也更繁琐。

     十分欣赏SRV的那种I_PUSH实现的将ip,icmp,udp等堆积成协议栈的方式--流机制,同时更乐于使用linux这种将一切都做好,但是还可以定制的OS。


 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1271801


相关文章
|
2月前
|
运维 Java Linux
Linux 下命令后台运行秘籍:无惧终端断开的魔法
本文详细介绍了在 Linux 系统下使命令不受终端断开影响、持续在后台运行的多种方法及其原理。包括使用 `nohup`、`setsid`、括号括起来、作业调度和 `screen` 等技巧,帮助读者提高工作效率,确保任务不被意外中断。
68 0
Linux 下命令后台运行秘籍:无惧终端断开的魔法
|
3月前
|
Shell Linux API
C语言在linux环境下执行终端命令
本文介绍了在Linux环境下使用C语言执行终端命令的方法。首先,文章描述了`system()`函数,其可以直接执行shell命令并返回结果。接着介绍了更强大的`popen()`函数,它允许程序与命令行命令交互,并详细说明了如何使用此函数及其配套的`pclose()`函数。此外,还讲解了`fork()`和`exec`系列函数,前者创建新进程,后者替换当前进程执行文件。最后,对比了`system()`与`exec`系列函数的区别,并针对不同场景推荐了合适的函数选择。
|
4月前
|
存储 安全 Linux
|
4月前
|
NoSQL Linux 开发工具
Linux终端革命:掌握这些命令,让工作速度飞跃提升!
本文介绍了Linux命令行操作效率提升的关键技巧,包括光标移动快捷键、Vim编辑器的高效使用、快速切换目录、跨服务器文件拷贝等。通过掌握`Ctrl + a`、`Ctrl + e`等快捷键可加快命令编辑;Vim的`:set nu`、`:20`等命令能提升文本编辑速度;`cd -`命令可在最近访问过的目录间快速切换;利用`nc`或`python -m SimpleHTTPServer`可实现在无密码权限时的文件传输。这些技巧帮助用户提高工作效率,简化日常工作流程。
101 1
|
4月前
|
Linux 数据安全/隐私保护
【Deepin 20 系统】Linux系统在开机时未进入系统前进入命令行界面(终端)
如何在Deepin 20系统启动时进入命令行界面(终端),通过在GRUB界面中编辑内核启动参数来引导系统进入多用户文本模式(运行级别3)。
314 1
|
4月前
|
存储 安全 Linux
Linux新手必备:关机重启、终端操作与快捷键大全
本文专为Linux新手打造,提供全面实用的指南,涵盖关机与重启命令(如`shutdown -h now`立即关机、`reboot`重启)、终端操作技巧(如使用`clear`清屏及Ctrl+L快捷键)、命令历史管理(利用`history`查看过往命令)及高效快捷键(如Ctrl+C复制、Ctrl+V粘贴),助您迅速掌握核心技能,成为Linux操作高手。
205 0
|
5月前
|
Ubuntu Linux 测试技术
Linux终端玩转bastet俄罗斯方块小游戏
【7月更文挑战第14天】Linux终端玩转bastet俄罗斯方块小游戏
93 7
|
4月前
|
Ubuntu Linux Shell
Linux 终端入门
Linux 终端入门
33 0
|
4月前
|
Linux
Linux——如何生成一个好看的终端欢迎语
Linux——如何生成一个好看的终端欢迎语
32 0
|
6月前
|
Linux 网络安全 开发工具
Linux 管理远程会话 screen:掌握终端的多任务操作
`Linux screen` 命令让多任务管理变得更简单,尤其在SSH连接远程服务器时。创建新会话如`screen -S backup`,查看会话`screen -ls`,退出`exit`。高级功能包括直接在会话中运行命令,如`screen vim memo.txt`,会话共享以协同工作,以及通过`screen -r`或`-D -r`重新连接或强制恢复断开的会话。提高效率,确保任务不间断运行。
103 1