本文内容来自The TTY demystified ,讲述了*NIX系统中TTY的历史与工做原理,看完后解决了我不少疑惑,因而作此翻译,与你们分享。php
审校:linux
V1.0 Sun May 13 12:42:01 CST 2018ios
一直以来,TTY子系统都是Linux/Unix设计中的一个关键点。不幸的是,这种重要性一般都被忽略了,而且也很难找到相关的介绍性文章。我认为,对Linux中TTYs的基础知识理解应是每个开发人员和高级使用者所必备的。shell
注意:你将阅读到的东西并非那么“优雅”。事实上,尽管在用户角度看很是实用,TTY子系统是由不少繁杂的东西和特殊状况组成的。为了理解它们的由来,咱们必须回到过去:vim
在1869年,证券报价机(stock ticker)被发明了。这是一台由打字机,一对长电缆和一个自动收录机打印机组成的电动机械机器,其目的是长距离实时传播股票的价格。这个概念逐渐演变成更快的基于ASCII的电传机(teletype)。Teletypes曾经在世界各地的大型网络中链接,并被称为Telex,其主要用于传输商业电报,但此时还没有链接到任何计算机。bash
与此同时,计算机(虽然仍是又笨重又昂贵)也开始支持多任务处理了,即可以实时和多个用户进行交互。当命令行最终取代了古老的批处理模型后,teletypes被用做输入和输出设备,由于它们在市场上很容易买到。服务器
可是在市场上有许多种电传机,它们的模型都略有不一样,所以须要计算机在软件层造成兼容。在UNIX世界中,使用的方法是让操做系统内核处理全部底层细节,例如字长,波特率,流量控制,奇偶校验,用于基本行编辑(rudimentary line)的控制代码等等。而视频终端(例如20世纪70年代后期出现的VT-100等)的光标移动,彩色输出和其余高级功能则留给了应用层。网络
如今,物理电传机和视频终端实际上已经灭绝了。除非你在访问博物馆或者你是一个硬件爱好者,不然你看到的全部TTY都是模拟视频终端,即软件仿真出来的终端。但咱们即将看到,这些远古的知识依然潜藏在现代TTY设计之中。session
以下图所示,用户在终端(terminal)打字(物理电传机),该终端经过一对电缆链接到计算机上的UART(通用异步接收器和发送器)。操做系统中有一个UART驱动程序,用于管理字节的物理传输,包括奇偶校验和流量控制。在一个原始的系统中,UART驱动程序会将传入的字节直接传送给某个应用程序进程,可是这种方法将缺少如下基本特征:
行编辑。大多数用户都会在输入时犯错,因此退格键会颇有用。这固然能够由应用程序自己来实现,可是根据UNIX设计“哲学”,应用程序应尽量保持简单。为了方便起见,操做系统提供了一个编辑缓冲区和一些基本的编辑命令(退格,清除单个单词,清除行,从新打印),这些命令在行规范(line discipline)内默认启用。高级应用程序能够经过将行规范设置为原始模式(raw mode)而不是默认的成熟或准则模式(cooked and canonical)来禁用这些功能。大多数交互程序(编辑器,邮件客户端,shell,及全部依赖curses
或readline
的程序)均以原始模式运行,并自行处理全部的行编辑命令。行规范还包含字符回显和回车换行(译者注:\r\n
和 \n
)间自动转换的选项。若是你喜欢,能够把它看做是一个原始的内核级sed(1)
。
另外,内核提供了几种不一样的行规范。一次只能将其中一个链接到给定的串行设备。行规范的默认规则称为N_TTY(drivers/char/n_tty.c
,若是你想继续探索的话)。其余的规则被用于其余目的,例如管理数据包交换(ppp,IrDA,串行鼠标),但这不在本文的讨论范围以内。
会话(Session)管理。用户可能想要同时运行多个程序,而且一次只与其中一个交互。若是一个程序进入无限循环,用户可能想要终止或挂起它。在后台启动的程序应该可以独立运行,直到它们尝试向终端写入(被挂起)。一样,用户的输入应该指向前台程序。对于这些功能,操做系统是在TTY驱动程序( TTY driver drivers/char/tty_io.c
)中实现的。
在操做系统中,若是已经进程有执行上下文,咱们就说它是“活着的”(有一个执行上下文),这也意味着它能够独立执行操做。而TTY驱动程序不是“活”的; 在面向对象的术语中,TTY驱动程序是被动对象(passive object)。它有一些数据字段和一些方法,但让它作某事的惟一方法是当它的某个方法从别的进程的上下文或内核中断处理程序中调用时。行规范(line discipline)一样是一个被动对象。
如今把它们放在一块儿看,UART驱动,行规范和TTY驱动这个三元组就能够被称为TTY设备,即咱们常说的TTY。用户进程能够经过在/dev
下操做相应的设备文件来影响任何TTY设备的行为。因为对设备文件写入权限是必需的,所以当用户登陆特定的TTY时,该用户必须成为设备文件的全部者——这一般由login(1)
程序完成,该程序以root权限运行。
上图中的物理电线也能够是长途电话线路(Modem),除了系统必须处理调制解调器挂断的状况,这并无带来其余的改变:
让咱们继续讨论典型的桌面系统。下图是Linux控制台的工做原理:
在上图中,TTY驱动和行规范的行为与前面的示例相似,但再也不有UART或物理终端。相反,软件仿真出视频终端(字符和图形字符属性帧缓冲器的复杂状态机),并最终被渲染到VGA显示器。
若是咱们在用户空间也进行终端仿真,状况会变得更加灵活(和抽象)。下图是xterm(1)
及其克隆的工做方式:
为了便于将终端仿真移入用户空间,同时仍保持TTY子系统(会话管理和行规范)的完整,伪终端被发明了出来(pseudo terminal 或 pty )。你可能已经猜到,当你开始在伪终端中运行伪终端时,事情变得更加复杂,例如 screen(1)
或 ssh(1)
。
如今让咱们退一步看看全部这些东西是如何和进程联系起来的。
Linux进程能够处于下面状态之一:
标志位 | 说明 |
---|---|
D | 不可中断睡眠(等待某个事件) |
S | 可中断睡眠(等待一些事件或者信号) |
T | 中止(收到了工做管理信号或者进程正在被调试器追踪) |
Z | 僵尸进程(被它的父进程终止可是没有被回收的进程) |
R | 运行或者可运行(在运行队列中) |
经过运行 ps l
, 你能够看到哪一个进程正在运行,以及哪一个进程正在睡眠。若是一个进程处于睡眠状态, WCHAN
列("wait channel", 等待队列的名字)将会告诉你这个进程正在等待哪一个内核事件。
$ ps l F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND 0 500 5942 5928 15 0 12916 1460 wait Ss pts/14 0:00 -/bin/bash 0 500 12235 5942 15 0 21004 3572 wait S+ pts/14 0:01 vim index.php 0 500 12580 12235 15 0 8080 1440 wait S+ pts/14 0:00 /bin/bash -c (ps l) >/tmp/v727757/1 2>&1 0 500 12581 12580 15 0 4412 824 - R+ pts/14 0:00 ps l
"wait
"等待队列对应于系统调用 wait(2)
,所以这个队列中的进程的子进程不论何时改变了状态,它们都会被移入运行状态。有两种睡眠状态:可中断睡眠和不可中断睡眠。可中断睡眠(最多见的状况)意味着当进程在等待队列中时,它实际上也可能因为收到了一个信号而被移入运行状态。若是你深刻到内核源码中,你将会发现每一个处理等待事件的内核源码都会检查在schedule()调用返回以后是否有待处理的信号,若是有,就从系统调用wait(2)
中返回。
在上面列出的 ps
结果中, STAT
列展现了每一个进程的当前状态。这一列中可能会显示一个或多个属性或标记:
s | 这个进程是会话领导 |
---|---|
+ | 这个进程是前台进程组的一员 |
这些属性被用于工做管理。
译者注:我以前翻译过两篇有关于进程标志的文章,可参考
Linux 进程状态标识 Process State Definition
Linux 可运行进程 Runnable Process Definition
当你按下 ^Z
挂起程序或者使用 &
在后台运行程序时,工做管理就发生了。一个工做(job)等同于一个进程组。shell内置的命令如 jobs
, fg
和 bg
能够用来管理一个会话(session)中的全部工做。每个会话是由一个会话领导(session leader),即shell来管理的,它会利用复杂的协议,例如信号和一些系统调用和内核打交道。
下面的例子解释了进程、工做、会话之间的关系。
Size: 45x13 Controlling process group: (101) Foreground process group: (103) UART configuration (忽略d, since this is an xterm): Baud rate, parity, word length and much more. Line discipline configuration: cooked/raw mode, linefeed correction, meaning of interrupt characters etc. Line discipline state: edit buffer (currently empty), cursor position within buffer etc.
Readable end (connected to PID 104 as file descriptor 0) Writable end (connected to PID 103 as file descriptor 1) Buffer
其中基本的思想是每一个管道都是一项工做,由于管道中的每一个进程都应该被同时进行操做(中止,恢复,终止)。这也是为何 kill(2)
容许你发送信号到整个进程组。默认状况下, fork(2)
将新建立的子进程放置在与其父进程相同的进程组中,例如,键盘上的 ^C
会影响父进程和子进程。可是,做为会话领导责任的一部分,每次启动管道时,shell都会建立一个新的进程组。
TTY驱动程序会记录前台进程组ID(PID),但这只能以被动方式进行。会话领导必须在必要时主动更新此信息。一样,TTY驱动程序会记录链接终端的属性(例如窗口大小),但这些信息必须由终端仿真程序甚至用户主动更新。
正如在上图中所看到的,几个进程将 /dev/pts/0
做为它们的标准输入。但只有前台工做 ls | sort
才会接收来自TTY的输入。一样,只有前台工做才被容许写入TTY设备(默认配置下)。若是cat
进程试图写入TTY,内核将使用信号将它挂起。
如今让咱们更近距离地看看内核中的TTY驱动、行规范和UART驱动是如何和用户态进程交互的。
UNIX文件,包括TTY设备文件,能够被读和写,而且因为许多TTY相关的操做都已经被定义,可使用神奇的 ioctl(2)
系统调用(UNIX的“瑞士军刀”)进行进一步操做。可是,ioctl
请求必须在进程内被初始化,所以它们不能在内核须要和应用进行异步通讯的场景下被使用。
在The Hitchhiker's Guide to the Galaxy(银河系漫游指南)中,Douglas Adams提到了一个“死星”,上面居住这一群消沉的人类和某种长着尖牙的动物。这些动物经过狠狠地咬人类的大腿来和人类交流(译者:喵喵喵?)。这和UNIX惊人地类似:在UNIX中,内核经过发送“瘫痪或者致命”的信号给用户进程来和进程通讯。一些进程可能可以拦截一些信号,而且尝试调整适应当前的状况,可是大多数进程不会这么作。
所以信号是一个“粗暴”的机制,它容许内核和进程进行异步通讯。UNIX中的信号定义是不规整或者不统一的;相反,每一个信号都是独特的,咱们必须单独研究它们。
你可使用命令 kill -l
来看看你的系统实现了哪些命令。结果看起来像下面这样:
$ kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX
正如你看到的,信号被从1开始的数字编号。然而当它们被在掩码中(例如在ps -s
的输出里)被使用时,最低有效位对应信号1。
这篇文章将会关注如下信号: SIGHUP
, SIGINT
, SIGQUIT
, SIGPIPE
, SIGCHLD
,SIGSTOP
, SIGCONT
, SIGTSTP
, SIGTTIN
, SIGTTOU
以及SIGWINCH
.
当检测到挂断(hangup)条件时,UART驱动会将SIGHUP
发送到整个会话。一般状况下,这会杀死全部进程。某些程序(如 nohup(1)
和 screen(1)
)会从其会话(和TTY)中分离,以便其子进程不会注意到挂断。
若是输入流中出现交互式注意( interactive attention )字符(一般为 ^C
,其代码为ASCII码3),那么SIGINT
就会由TTY驱动发送到当前的前台工做,除非此配置已被关闭。任何有权访问TTY设备的人均可以更改交互式注意字符并开关此配置; 此外,会话管理器会跟踪每一个工做的TTY配置,并在有工做切换时更新TTY。
SIGQUIT
的工做方式和 SIGINT
类似, 可是使用的字符是 ^\
而且默认操做不一样。
内核会给每个试图往没有读取者的管道中写数据的进程发送 SIGPIPE
信号。 这是颇有用的,由于没有这个信号的话,相似 yes | head
这样的工做就永远不会中止了。
当进程死亡或更改状态(中止/继续)时,内核会向其父进程发送一个 SIGCHLD
。 SIGCHLD
信号携带着终止进程的附加信息,即进程标识,用户标识,退出状态(或终止信号)以及一些执行时间的统计信息。会话领导(shell)使用这个信号追踪其工做。
该信号将无条件地挂起接收者,即其信号动做不能被从新配置。要注意的是,在工做控制期间,SIGSTOP
不会由内核发送。相反,^Z
一般会触发一个 SIGTSTP
,它能够被应用程序拦截。而后应用程序能够进行例如将光标移动到屏幕底部等操做,而后使用SIGSTOP
将本身置于睡眠状态。
SIGCONT
将“反挂起”(un-suspend,continue)一个中止的进程。当用户调用fg
命令时,它会由shell发送出去。因为 SIGSTOP
不能被应用程序拦截,所以意料以外的SIGCONT
信号可能代表该进程在某段时间以前被挂起,而后被唤醒。
SIGTSTP
与 SIGINT
和 SIGQUIT
的工做原理类似,可是它使用的是 ^Z
字符,而且默认的操做是挂起进程。
若是一个后台工做中的进程尝试从TTY设备中进行读取,TTY会向整个工做(组)发送一个 SIGTTIN
信号,这一般会挂起这个工做。
若是一个后台工做中的进程尝试向TTY设备中进行写入,TTY会向整个工做(组)发送一个 SIGTTIN
信号,这一般会挂起这个工做。这种行为能够经过配置TTY关闭。
如前所述,TTY设备会记录终端的窗口大小,但这些信息须要手动更新。只要发生这种更新,TTY设备就会向前台工做发送 SIGWINCH
。行为良好的交互式应用程序(例如编辑器)会对此做出反应,从TTY设备获取新的终端窗口大小并重绘GUI。
译者注:我以前翻译过一篇有关于进程和信号的文章,可参考
假设你正在编辑(基于终端的)编辑器中的文件。此时光标位于屏幕中间的某个位置,编辑器正在执行一些任务,例如对大文件执行搜索和替换操做。如今你按 ^Z
,因为行规范已被配置为拦截此字符(^Z
是一个单字节,ASCII码为26),所以你无需等待编辑器完成其任务而后从TTY设备开始读取。相反,行规范子系统会当即将 SIGTSTP
发送到前台进程组。该进程组包含编辑器以及由其建立的任何子进程。
编辑器为 SIGTSTP
安装(install)了一个信号处理程序,所以内核将程序执行流转移到信号处理程序代码中。经过将相应的控制序列写入TTY设备,该代码将光标移动到屏幕的最后一行。因为编辑器仍处于前台,控制序列按要求发送。随后编辑器会将 SIGSTOP
发送到其本身的进程组(正如上节信号中说的那样)。
编辑器如今已经中止,SIGCHLD
信号向会话领导通告这个事件,其中包括该进程的ID。当前台工做中的全部进程都被挂起时,会话领导从TTY设备读取当前配置,并将其存储起来以供之后使用。会话领导继续使用 ioctl
调用将其自身安装为TTY的当前前台进程组。而后,它会打印相似 "[1]+ Stopped" 的内容,以通知用户工做已暂停。
此时, ps(1)
会告诉你编辑器进程处于中止状态(“T
”)。若是咱们试图使用内置shell命令bg
或使用 kill(1)
向进程发送 SIGCONT
来唤醒它,编辑器将开始执行其 SIGCONT
信号处理程序。而该处理程序会尝试经过写入TTY设备来从新绘制编辑器的GUI界面。但如今编辑器是一个后台工做,TTY设备将不容许它进行写入。因此,TTY会给编辑器发送 SIGTTOU
信号,令其再次中止。这个事件将经过使用 SIGCHLD
传递给会话领导(shell),而shell会再次向终端写入“[1] + Stopped”。
可是,当咱们键入fg
时,shell首先恢复先前保存的行规范配置。它通知TTY驱动编辑器工做应该从如今起做为前台工做。最后,它向进程组发送一个SIGCONT
信号。编辑器试图重绘它的GUI,此次它不会被SIGTTOU
中断,由于它如今是前台工做的一部分。
译者注:
在 xterm
中运行 yes
,你会看到不少“y
”出如今你眼前。天然,yes
进程可以很快的产生y
,以致于xterm
来不及进行帧缓冲区更新,与X服务器通讯(译者注:X Window System)以便滚动窗口等操做。那么这些程序是如何进行配合的呢?
答案在于I/O阻塞。伪终端只能在其内核缓冲区内保存必定数量的数据,当该缓冲区满而且 yes
尝试调用 write(2)
时,write(2)
将被阻止,并将yes
进程移至可中断的睡眠状态,直到xterm
可以读取缓冲中的字节。
若是TTY链接到串行端口,也会发生一样的状况。假设 yes
可以以比9600波特的速率传输数据,可是若是串行端口被限制在低的多速度上,内核缓冲区很快就会被填满,而且任何后续的 write(2)
调用都会致使进程睡眠(或收到返回的错误号 EAGAIN
,若是进程要求非阻塞I/O的话)。
若是我告诉过你,即便内核缓冲区中还有剩余空间,也能够主动地将TTY置于阻塞状态,更进一步的说,每一个试图 write(2)
到TTY的进程都会自动阻塞。那么这种功能的用途是什么?
假设咱们正在以9600波特率的速度与一些旧的VT-100通讯。咱们刚刚发送了一个复杂的控制序列,要求终端滚动显示。此时,终端会因执行滚动操做没法以9600波特的全速率接收新数据。实际上,UART仍然以9600波特运行,但终端中没有足够的缓冲空间来保持接收字符。如今就是将TTY置于阻塞状态的好时机。可是,咱们该如何从终端作到这一点?
咱们已经看到,TTY设备能够被配置为给某些数据字节特殊的处理。例如,在默认配置中,收到的 ^C
字节不会经过read(2)
传递给应用程序,而是会将 SIGINT
信号传递到前台工做。相似地,能够将TTY配置为对中止流和开始流作出反应,一般分别是 ^S
(ASCII码19)和 ^Q
(ASCII码17)。旧的硬件终端会自动传输这些字节,并指望操做系统相应地调节其数据流。这被称为流控制,这就是为何当你偶然按下 ^S
时,你的xterm
会“锁定”。
这里有一个重要的区别:写入因为流控制而中止的TTY,或者因为缺乏内核缓冲区空间,只会阻塞你的进程,而从后台工做中写入TTY将致使SIGTTOU
暂停整个进程组。我不知道为何UNIX的设计师必须发明 SIGTTOU
和 SIGTTIN
,而不是仅仅依靠I/O阻塞,但我最好的猜想是负责工做控制的TTY驱动是为了监视和操纵整个工做——而不是其中的单个进程。
为了找出你的shell调用的控制TTY,你可使用前面说过的ps l
,或者运行tty(1)
命令。
进程可使用 ioctl(2)
读取或修改打开的TTY设备的配置。 该API在 tty_ioctl(4)
中有描述。 因为它是Linux应用程序和内核之间的二进制接口的一部分,它将在Linux版本迭代中获得保持。 可是,该接口是不可移植的,应用程序应该使用 termios(3)
手册页中描述的POSIX包装器。
我不会详细讨论 termios(3)
接口的细节,可是若是你正在编写C程序并但愿在 ^C
变成 SIGINT
以前拦截 ^C
,或者禁用行规范或字符回显,或将更改一个串的口波特率,关闭流控制等,你就会发现你须要上述的手册页(man page)。
这里还有一个名为 stty(1)
的命令行工具来操做TTY设备。 它使用的是 termios(3)
API。
让咱们试试吧!
$ stty -a speed 38400 baud; rows 73; columns 238; line = 0; intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0; -parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts -ignbrk brkint ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany imaxbel -iutf8 opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
-a
参数是让stty
显示全部的设置。默认状况下,它将查看链接到shell的TTY设备,但能够经过-F
指定其余的设备。
在上面显示出的设置中,一些会改变UART参数,一些会影响行规范,一些则用于工做控制。咱们先来看看第一行:
属性 | 设备 | 说明 |
---|---|---|
rows, columns | TTY驱动 | 该TTY设备的终端的大小(以字符做为基准)。基本上,它只是内核空间中的一对变量,你能够自由设置和获取。设置它们将致使TTY驱动程序向前台工做发送SIGWINCH 。 |
line | 行规范 | 该TTY设备的行规范. 0 表明 N_TTY . 全部可用的数值在 /proc/tty/ldiscs 中有列出. 使用未列出的数值等价于使用 N_TTY , 可是不要依赖于这一点. |
speed | UART | 波特率。伪终端忽略这个参数。 |
尝试如下操做:启动一个 xterm
。记下它的TTY设备( tty
命令得到)及其窗口大小(由stty -a得到
)。接着在xterm
中启动 vim
(或其余一些全屏终端应用程序)。vim
编辑器会向TTY设备查询当前的终端窗口大小,以此填充整个窗口。如今,从另外一个shell窗口输入:
stty -F X rows Y
其中X是刚才得到的TTY设备,Y是终端高度的一半。这将更新内核内存中的TTY数据结构,并向编辑器发送 SIGWINCH
,vim
将使用可用窗口区域的上半部分重绘GUI。
stty -a
输出的第二行列出了全部特殊的字符,开一个新的 xterm
而后试试这个:
stty intr o
如今,"o
"而不是 ^C
将向前台工做发送 SIGINT
。尝试运行一些程序,好比 cat
,并看看你能不能用 ^C
杀死它。而后,尝试在其中输入“hello”。
有时候,你可能会遇到退格键不起做用的Unix系统——当终端仿真器发送与TTY设备中的擦除设置不匹配的退格码(ASCII 8或ASCII 127)时,就会发生这种状况。为了解决这个问题,请设置 stty erase ^H
(ASCII 8)或 stty erase ^?
(ASCII 127)。要注意的是,许多终端应用程序使用readline
,这使得行规范处于原始模式,即这些应用程序不受到影响。
最后,stty -a
列出了一系列开关(没有特定顺序列出)。其中一些与UART相关,一些影响线路规范行为,一些用于流量控制,一些用于工做控制。短划线( - )表示开关关闭;不然它是开着的。全部的开关都在stty(1)手册页中进行了解释,因此我将简单地提一下:
icanon用于将行规范切换为规则(基于行)模式。在一个新的 xterm
中试试这个,关闭这个模式:
stty -icanon; cat
如今全部的行编辑字符,例如退格或者^U
都会中止工做。另外注意到cat
会一次接受一个字符(并连续输出),而不是一次接受一行。
echo 是启用字符回显的开关(默认也是开着的)。如今从新启动规则模式(stty icanon
)而后试试这个:
stty -echo; cat
当你输入时,你的终端仿真器将信息传送给内核,而内核一般会将相同的信息回显给终端仿真器,以便让你看到以前键入的内容。如今没有了字符回显,你就不能看到你输入的内容。不过咱们处于熟化(cooked)模式,因此行编辑工具仍在工做。一旦你按下回车键,行规范就会把编辑缓冲区的数据传送给cat,显示出你刚刚键入的内容。
tostop 是控制后台进程是否容许写入终端的开关,先试试这个:
stty tostop; (sleep 5; echo hello, world) &
&
会使得该命令做为后台工做运行。五秒钟后,该工做将尝试写入TTY。 TTY驱动程序将使用 SIGTTOU
将其挂起,而且shell可能会当即报告此事件,或者发出别的提示。如今尝试下面的代码:
stty -tostop; (sleep 5; echo hello, world) &
五秒钟以后,后台工做会在你当前的光标位置输出 hello, world
。
最后, stty sane
会将你的TTY设置成一个相对合理的配置。
我但愿这篇文章为你提供了足够的信息去了解TTY驱动和行规范,以及它们与终端,行编辑和工做控制之间的关系。 更多细节能够在我提到的各类手册页以及glibc手册(info libc
,"Job Control")中找到。
最后,尽管我没有足够的时间来回答全部问题,但我欢迎任何对本网站上的其余网页提出的反馈意见。 谢谢阅读!
译者注:更多有关于tty、shell、console的知识,能够参考