虽然Linux内核是由C语言写的,但处处体现面向对象的设计思想,这对很多只会C语言的朋友来说,理解比较困难,尤其是tty体系,涉及很多混乱的概念。
在上古时期,tty和terminal和consle这三个概念都对应着各种的硬件
console:控制台
在早期的电脑上,往往具有带有大量开关和指示灯的面板,可以对电脑进行一些底层的操作,这个面板就叫做Console。一台电脑通常只能有一个Console,很多时候是电脑主机的一部分,和CPU共享一个机柜。
terminal:终端
控制台可以实现对计算机的完全操控,用来管理计算机,但不方便给用户服务。后来计算机技术发展,计算能力不断提升并出现了多用户操作系统(特别是UNIX),允许每个用户通过终端设备(Terminal)与主机连接,操作系统管理员给每个用户分配一个账户,“登录”到系统获得计算机使用权,这时计算机才算实现了服务用途。
terminal表示一台能够用来和电脑交互的设备——通常有显示器和键盘。
console和terminal关系:console在主机上,只有1个,terminal连接到主机,可以有好多个
tty:teletypewriter
字面意思电传打字机。属于terminal的一种。Linux在内核中使用了这个名字(后面详解)。
插讲Shell
部分入门的朋友,可能把shell误认为是终端。
Shell是UNIX/Linux系统中最为重要的应用程序之一,负责解释执行用户指令,打印结果,和用户交互。
终端自身并不执行用户输入的命令,它只是负责把输入的内容传送到主机系统(键盘等输入设备),并把主机系统返回的结果呈现给用户(屏幕已经早期的打印纸)。负责解释执行用户输入的命令并返回结果的,正是Shell!它是沟通用户和系统内核的中间桥梁。
广义上的Shell可以是图形界面的也可以是命令行界面的。
随着发展,terminal、tty、console这几个概念开始混乱了。
电传打字机已基本淘汰,控制台、终端不再是各自独立的外设,PC上的键盘、显示器被整合为控制台/终端。
terminal不止有电传打字机这一种,还有带屏幕等很多形式。
Linux为了屏蔽这些差异,引入了tty driver这一层:
硬件驱动程序-应用程序,变为硬件驱动程序-tty driver-应用程序,这是驱动涉及中常见的分离思想,把共性抽象出来。即,把原来的一个硬件驱动程序分为了两部分。
终端与内核的交互:
真正的重点来了:终端如何与程序交互?
例如:我们输入退格键,他本身只是个值,如何知道作用是退格?
linux引入了行规程(line discipline),相当于一个过滤器,或者叫解释器。
这当然可以由应用程序本身来实现,但是根据UNIX设计“哲学”,应用程序应尽可能保持简单。
为了方便起见,操作系统提供了一个编辑缓冲区和一些基本的编辑命令(退格,清除单个单词,清除行,重新打印),这些命令在行规程(line discipline)内默认启用。
应用程序可以通过将行规范设置为原始模式(raw mode)而不是默认的成熟或准则模式(cooked and canonical)来禁用这些功能,也就是得到原始的值。
大多数交互程序(编辑器,邮件客户端,shell,及所有依赖curses
或readline
的程序)均以原始模式运行,并自行处理所有的行编辑命令。行规范还包含字符回显和回车换行(译者注:\r\n
和 \n
)间自动转换的选项。
即把硬件驱动程序-tty-应用程序中的tty又给拆分,变成行规程(line discipline)和tty driver
即:硬件driver-行规程(line discipline)-tty driver-应用程序
这里是一个比较容易混淆的地方,tty driver并不是一个真正的驱动,只是抽象出一些接口,对于应用程序而言,他就是个driver,对于应用程序,底层的差异是被屏蔽了的,相当于:
tty硬件-tty硬件driver-应用程序
所以其实也并不把他叫driver,而一般叫tty core核心
所以这种说法更加清晰:硬件-硬件drvier-行规程line disciple-tty core-应用程序
我们知道远古电传打字机是通过uart与主机相连。
人通过打字机(terminal)输入,uart与主机连,经过行规程(line discipline),到达tty,程序读取tty。
UART驱动,行规程和TTY驱动这个三元组就可以被称为TTY设备,即我们常说的TTY。
这其实也就是对应我们现在的tty驱动,行规程,tty核心三部分,共同组成tty设备。
虚拟终端(Virtual Terminal)
一台PC通常只有一套键盘和显示器,也就是只有一套物理终端设备,Linux内核将这一套键盘和显示器映射为6个字符终端设备文件,即/dev/tty1~tty6,称为“虚拟终端(Virtual Terminal)”,可通过Ctrl-Alt-F1~F6切换。
/dev/tty0则指向用户当前正在使用的虚拟终端,如用户切换到/dev/tty4,那么/dev/tty0就指向/dev/tty4。
串口终端(Serial Port Terminal)
后来计算机发展出串行端口(Serial Port),彼时串口的最大用途就是连接终端,计算机就把连接到串口的外设看作字符设备,外设称为“串口终端(Serial Port Terminal)”,串口在Linux系统中对应着设备/dev/ttyS1、/dev/ttyS2......等。
上文中的虚拟终端其实也是真实终端,只不过是1化6,下面真正的虚拟来了:
终端模拟器(Terminal Emulator)
基于系统中已有的键盘、显示驱动而构建的图形界面程序,其根本用途只有一个——模拟真实终端的功能。例如我们在windows上通过MobaXterm软件连接到linux主机,MobaXterm就是个终端模拟器。在Linux桌面版系统中也有自带的终端模拟器。
终端模拟器为啥叫模拟器呢? 因为真正的终端是全屏显示的黑乎乎的不带窗口的那种(按ctrl+alt+f1),这里带了窗口,是基于linux的X窗口系统上模拟出来的终端设备。
伪终端:pseudo terminal
伪终端(pseudo tty,缩写为pty),是UNIX/Linux内核中一对双向互联的设备,分为主设备(pty master)和从设备(pty slave),也称作“伪终端对(pty pair)”。
终端模拟器(terminal emulator) 是运行在内核的模块,我们也可以让终端模拟程序运行在用户区。运行在用户区的终端模拟程序,就被称为伪终端(pseudo terminal, PTY)。
我们在Linux系统中按住ctrl+alt+t打开一个terminal
终端模拟器模拟出一个terminal,这个terminal通过 伪终端:pseudo terminal,再通过行规程,再通过tty,再与程序交互。
Linux 中为什么要提出伪终端这个概念呢?shell 等命令行程序不可以直接从显示器和键盘读取数据吗?
可以,但那就是真实的tty了,但是我们只有一个屏幕啊,想要开多个终端怎么办。
如果是远程登陆,终端虽然通过基于TCP/IP协议的socket接口与主机连接了,但远程主机上的程序的标准输入、标准输出、标准错误无法关联到socket上。(程序结果依然在本地显示,除非在程序中实现回传,这就增加了程序的复杂度)
为了同屏运行多个终端模拟器、并实现远程登录,就有了伪终端,实现了多路复用,也实现了脱离主机的硬件。
伪终端本质上是运行在用户态的终端模拟器创建的一对字符设备。主设备名为/dev/ptmx ,从设备名为/dev/pts0、/dev/pts1.../dev/ptsN等。
/dev/ptmx 是一个字符设备文件,当进程打开 /dev/ptmx 文件时,进程会同时获得一个指向 pseudoterminal master(ptm)的文件描述符和一个在 /dev/pts 目录中创建的 pseudoterminal slave(pts) 设备。(就像管道的两端)
通过打开 /dev/ptmx 文件获得的每个文件描述符都是一个独立的 ptm,它有自己关联的 pts,ptmx(可以认为内存中有一个 ptmx 对象)在内部会维护该文件描述符和 pts 的对应关系,对这个文件描述符的读写都会被 ptmx 转发到对应的 pts。
我们可以通过 lsof 命令查看 ptmx 打开的文件描述符:
$ sudo lsof /dev/ptmx
归纳:
终端分为真终端(tty1-6,ttySn等)和伪终端
真终端中的tty1-6又称虚拟终端,因为实际上只有一套输入输出。
console:用来显示系统消息的终端,终端可以成为console
tty driver最初是用来抽象出tty硬件设备,后来发展成行规程+tty core,tty driver一词用来表示硬件部分驱动
参考文章:
解密TTY - QiuhaoLi - 博客园