TTY解密(The TTY demystified)

The TTY subsystem is central to the design of Linux, and UNIX in general. Unfortunately, its importance is often overlooked, and it is difficult to find good introductory articles about it. I believe that a basic understanding of TTYs in Linux is essential for the developer and the advanced user.

在Linux系统和UNIX系统的设计中,TTY子系统都是处于重要地位的。但不幸的是,它的重要性往往被忽视了,并且很难找到好的介绍TTY系统的资料。我认为无论对Linux系统的开发者还是是高级用户而言,都有必要对TTY系统有一个基本的认识。

早期的电传打字机

Beware, though: What you are about to see is not particularly elegant. In fact, the TTY subsystem — while quite functional from a user's point of view — is a twisty little mess of special cases. To understand how this came to be, we have to go back in time.

然而,不得不提的是:TTY子系统并不是你想的那样优雅和简洁。事实上,尽管用户从功能性的角度来看,它功能非常的强大以及实用,但是在一些特殊情况下,关于TTY子系统的概念(比如通过网络登录系统)甚至有些混乱。为了解释这一切到底是为什么,我们必须回到过去看看TTY的发展历史。

History 历史

In 1869, the stock ticker was invented. It was an electro-mechanical machine consisting of a typewriter, a long pair of wires and a ticker tape printer, and its purpose was to distribute stock prices over long distances in realtime. This concept gradually evolved into the faster, ASCII-based teletype. Teletypes were once connected across the world in a large network, called Telex, which was used for transferring commercial telegrams, but the teletypes weren't connected to any computers yet.

早在1869年,人们发明了股票报价机。它是由一个打字机通过两根很长的电线与远端的纸条打印机相连组成的电子设备,它被发明的目的主要是向千里之外的人们实时地发布股票价格。逐渐的,股票报价机被演变成了电传打字机,电传打印机的速度更快,并且它是基于ASCII编码的。世界上的所有电传打字机曾一度被连接在一起,组成了一个叫做Telex的大型网络,被广泛的用于传输商业电报,但是那个时候的电传打字机是不与任何计算机相连的。

Meanwhile, however, the computers — still quite large and primitive, but able to multitask — were becoming powerful enough to be able to interact with users in realtime. When the command line eventually replaced the old batch processing model, teletypes were used as input and output devices, because they were readily available on the market.

同时期的计算机却依然是个庞然大物,并且还处于很原始/简单的发展阶段,但是却已经可以支持多任务处理了 -- 多任务功能已经足以使计算机可以实时地同多人交互作业。再后来,当命令行逐渐地替代了之前的批处理模式时,电传打字机被用来作为计算机的输入/输出设备,因为市面上有很多现成的电传打字机可以使用,且价格低廉。

There was a plethora of teletype models around, all slightly different, so some kind of software compatibility layer was called for. In the UNIX world, the approach was to let the operating system kernel handle all the low-level details, such as word length, baud rate, flow control, parity, control codes for rudimentary line editing and so on. Fancy cursor movements, colour output and other advanced features made possible in the late 1970s by solid state video terminals such as the VT-100, were left to the applications.

DEC VT100 terminal

然而市面充斥着各种各样的电传打字机,他们或多或少有些不同之处,所以急切的需要一个软件兼容层(software compatibility layer)来处理这些电传打字机的不同细节特性。在UNIX系统中,解决的办法是让操作系统内核来处理这些繁杂的终端细节,比如字长(word length),波特率(baud rate),流控制(flow control),奇偶校验(parity),以及一些基本行编辑(line editing)的控制码(control codes),当然还包括很多其它终端特性。到了上个世纪70年代后期,视频终端(video terminals)的出现(比如VT-100视频终端),使得艳丽的光标移动,颜色输出以及其他终端高级特性成为可能,同样的,这些新的终端特性也完全可以由内核来配置决定。

In present time, we find ourselves in a world where physical teletypes and video terminals are practically extinct. Unless you visit a museum or a hardware enthusiast, all the TTYs you're likely to see will be emulated video terminals — software simulations of the real thing. But as we shall see, the legacy from the old cast-iron beasts is still lurking beneath the surface.

发展到现在,生活中我们已经几乎看不到真实的电传打字机和视频终端了,除非你去博物馆或者去硬件收集发烧友那里才能看到。现在你所能见到的终端都是模拟的,模拟的视频终端(emulated video terminals) -- 也就是用软件模拟真实的终端。但是在使用模拟终端的过程中,我们会发现,明白一些看似很古老的概念对于我们的使用和学习大有裨益。

A user types at a terminal (a physical teletype). This terminal is connected through a pair of wires to a UART (Universal Asynchronous Receiver and Transmitter) on the computer. The operating system contains a UART driver which manages the physical transmission of bytes, including parity checks and flow control. In a naïve system, the UART driver would then deliver the incoming bytes directly to some application process. But such an approach would lack the following essential features:

用户通过终端(物理电传打字机)键入数据信息传递给计算机。终端是通过两根串行线与计算机的UART(通用异步接收发器)相连的,用户键入的信息就是通过这两根串行线传递给计算机。而操作系统的UART驱动程序是专门处理这些数据信息的物理传输的,包括对这些数据进行奇偶校验和流控制。在早期的系统中,UART驱动程序会直接将用户键入的这些数据信息不做任何处理地传递给应用程序,但是这样不够灵活,会导致失去以下很重要的几个特性:

Line editing. Most users make mistakes while typing, so a backspace key is often useful. This could of course be implemented by the applications themselves, but in accordance with the UNIX design philosophy, applications should be kept as simple as possible. So as a convenience, the operating system provides an editing buffer and some rudimentary editing commands (backspace, erase word, clear line, reprint), which are enabled by default inside the line discipline. Advanced applications may disable these features by putting the line discipline in raw mode instead of the default cooked (or canonical) mode. Most interactive applications (editors, mail user agents, shells, all programs relying on curses or readline) run in raw mode, and handle all the line editing commands themselves. The line discipline also contains options for character echoing and automatic conversion between carriage returns and linefeeds. Think of it as a primitive kernel-level sed(1), if you like.

行编辑功能。举个大家习以为常的例子: 我们平时在打字过程中,肯定有打“错字”的时候,那么这个时候退格键(backspace)就格外有用了。诸如此类的功能,应用软件本身当然是可以实现的,但是这有悖于UNIX操作系统的设计原则: 应用软件应该尽可能的简单。所以为了方便,操作系统在终端子系统中专门加入了线路规程(line discipline)中间层,线路规程提供有编辑缓冲区(输入队列和输出队列)使得行编辑以及很多基本的编辑命令(backspace退格,erase word删除单词,clear line删除整行,reprint)成为可能。当然了,上层的应用程序可以通过设置线路规程为原始模式(非规范模式输入处理)来取消这些功能(线路规程默认的模式是规范模式输入处理,这种模式下支持行编辑以及编辑命令)。绝大多数的交互式应用程序(例如编辑器,电子邮件,shell,以及很多依赖于curses和readline的应用程序)都是运行在原始模式下,也就是说由它们自己来处理编辑命令。线路规程还包括字符回显功能(stty echo),特殊字符间的转换功能(例如在回车和换行之间自动转换 -- 不同的操作系统对这个两个字符有不同的处理http://www.cnblogs.com/clarkchen/archive/2011/06/02/2068609.html)。可以把线路规程想象成内核级的sed工具(sed是一个流编辑器应用软件)。

Incidentally, the kernel provides several different line disciplines. Only one of them is attached to a given serial device at a time. The default discipline, which provides line editing, is called N_TTY (drivers/char/n_tty.c, if you're feeling adventurous). Other disciplines are used for other purposes, such as managing packet switched data (ppp, IrDA, serial mice), but that is outside the scope of this article.

值得一提的是,操作系统内核一般都会提供几个不同的线路规程模块。每一种串行设备对应一种线路规程。默认的线路规程提供行编辑功能,被称为N_TTY(drivers/char/n_tty.c)。其余的线路规程有其特有的功能,比如用来管理数据包数据交换(ppp,IrDA,serial mice),不过这里不做介绍。

Session management. The user probably wants to run several programs simultaneously, and interact with them one at a time. If a program goes into an endless loop, the user may want to kill it or suspend it. Programs that are started in the background should be able to execute until they try to write to the terminal, at which point they should be suspended. Likewise, user input should be directed to the foreground program only. The operating system implements these features in the TTY driver (drivers/char/tty_io.c).

会话管理。考虑下面几个情景:(1)一个用户有时想同时运行几个程序,但是某一时刻只能与其中的一个交互。(2)如果一个程序陷入死循环,那么用户可能想将其杀死(Ctrl+C或者kill -9)或者挂起(Ctrl+Z)。(3)当一个后台进程试图往它的控制终端(该控制终端的设置中应该包括一项:不允许后台作业往它的控制终端写数据,可通过stty tostop来设置 APUE9.8)写数据时(或者读终端数据),操作系统就会将其挂起(终端驱动程序向后台作业发送SIGTTIN或者SIGTTOU信号,这两个信号会暂时停止此后台作业 APUE 9.8)。(4)此外,用户通过终端输入的信息应该只能直接发送给前台进程。诸如此类的功能,操作系统是在TTY驱动程序中实现的(drivers/char/tty_io.c)。

An operating system process is "alive" (has an execution context), which means that it can perform actions. The TTY driver is not alive; in object oriented terminology, the TTY driver is a passive object. It has some data fields and some methods, but the only way it can actually do something is when one of its methods gets called from the context of a process or a kernel interrupt handler. The line discipline is likewise a passive entity.

我们知道,只有当一个进程的状态是“活跃的(alive)”时,它才能执行相应的动作。然而TTY驱动程序的状态却不是处于“活跃”状态,如果用面向对象的术语来说的话,就是TTY驱动程序是个被动对象,它有属于自己的数据和方法,只有当进程调用这些方法或者发生内核中断时这些方法才会被执行。同样的,线路规程也是个被动对象。

Together, a particular triplet of UART driver, line discipline instance and TTY driver may be referred to as a TTY device, or sometimes just TTY. A user process can affect the behaviour of any TTY device by manipulating the corresponding device file under /dev. Write permissions to the device file are required, so when a user logs in on a particular TTY, that user must become the owner of the device file. This is traditionally done by the login(1) program, which runs with root privileges.

因此,通常所说的TTY设备(或者直接简称TTY)就是由UART驱动,线路规程和TTY驱动程序三部分组成。通常系统启动时,会自动通过login程序(root权限)让用户登录终端,使用户拥有该终端的所有权限,那么用户就可以根据/dev目录下的相应tty文件来操作该TTY设备了。

The physical line in the previous diagram could of course be a long-distance phone line:

在前面的图中,实体线(physical line)当然也可以是距离非常远的电话线(通过调制解调器相连),如下图所示。

This does not change much, except that the system now has to handle a modem hangup situation as well.

其实并没有改变多少,只是操作系统需要额外处理调制解调器(modem)断开连接这种情况(如果终端接口检测到调制解调器断开连接,则将挂断信号SIGHUP发送给控制进程(会话首进程)APUE 9.6)。

Let's move on to a typical desktop system. This is how the Linux console works:

现在让我们来看在一个典型的桌面系统中linux的控制台子系统是如何工作的:

The TTY driver and line discipline behave just like in the previous examples, but there is no UART or physical terminal involved anymore. Instead, a video terminal (a complex state machine including a frame buffer of characters and graphical character attributes) is emulated in software, and rendered to a VGA display.

可以看到,TTY驱动程序和线路规程依然保留使用,但是已经不存在UART驱动程序或者物理终端了。取而代之的却是一个软件模拟的视频终端(真实的视频终端是一个复杂的状态机,拥有字符图像缓冲功能等特性),由键盘输入内容,最终显示在VGA显示器上。

The console subsystem is somewhat rigid. Things get more flexible (and abstract) if we move the terminal emulation into userland. This is how xterm(1) and its clones work:

如果能将终端模拟器提取到用户空间,那么控制台子系统就变得更灵活了(当然也变得更加的抽象了)。大家熟知的xterm就是这么工作的:

To facilitate moving the terminal emulation into userland, while still keeping the TTY subsystem (session management and line discipline) intact, the pseudo terminal or pty was invented. And as you may have guessed, things get even more complicated when you start running pseudo terminals inside pseudo terminals, à la screen(1) or ssh(1).

为了实现将终端模拟器提取到用户空间,而又不改变TTY子系统(包括会话管理和线路规程),伪终端(或称为pty)被开发出来广泛使用。也许你也可以猜到,当你在伪终端中再打开伪终端的话,那么事情就变得越来越复杂了。典型的程序有screen和ssh。

Now let's take a step back and see how all of this fits into the process model.

现在让我们来看看这一切是怎么遵循操作系统进程模型的。
A Linux process can be in one of the following states:
在linux操作系统中,一个进程的状态只可能是以下状态的一种:

R Running or runnable (on run queue)
D Uninterruptible sleep (waiting for some event)
S Interruptible sleep (waiting for some event or signal)
T Stopped, either by a job control signal or because it is being traced by a debugger.
Z Zombie process, terminated but not yet reaped by its parent.

R 正在运行或者说是可运行的(正处于运行队列中等待)
D 不可中断睡眠状态(正在等待某个事件的发生)
S 可中断睡眠状态(正在等待某个事件的发生或者信号)
T 停止(挂起)状态,也许是收到作业控制信号的缘故,也有可能是因为该进程正在被调试器调试
Z 僵尸进程,指的是一个进程已经终止,但是其系统资源还未被其父进程收回

By running ps l, you can see which processes are running, and which are sleeping. If a process is sleeping, the WCHAN column ("wait channel", the name of the wait queue) will tell you what kernel event the process is waiting for.
    通过运行ps l命令,可以观察到哪些进程正在运行,哪些进程处于睡眠状态。如果一个程序处于休眠状态,那么WCHAN列(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

The "wait" wait queue corresponds to the wait(2) syscall, so these processes will be moved to the running state whenever there's a state change in one of their child processes. There are two sleeping states: Interruptible sleep and uninterruptible sleep. Interruptible sleep (the most common case) means that while the process is part of a wait queue, it may actually also be moved to the running state when a signal is sent to it. If you look inside the kernel source code, you will find that any kernel code which is waiting for an event must check if a signal is pending after schedule() returns, and abort the syscall in that case.
    列出的几个“wait”对应的是wait(2)系统调用,所以当这些进程的任一个子进程状态改变时,那么进程的状态就会从休眠状态转换成运行状态。通常所说的睡眠状态在linux系统中包括两种:可中断睡眠和不可中断睡眠。可中断睡眠(绝大多数情况下是可中断睡眠)意味着尽管进程处于等待队列,但是却可以被信号将其唤醒。例如,发起一个阻塞的系统调用(譬如,read()终端设备会阻塞到有数据输入为止)会导致进程进入可中断睡眠状态,内核随后通过进程调度函数schedule()调度运行别的进程,但在每次进程调度的时候,schedule()函数在返回之前,都会检测处于可中断睡眠状态的进程是否有未决的信号(记录在该进程的PCB中),如果有,则先处理信号,之后系统调用返回失败,并将errno设置为EINTR。

In the ps listing above, the STAT column displays the current state of each process. The same column may also contain one or more attributes, or flags:

在上面的ps输出结果中,STAT列显示的是进程的当前状态。这一列一般还会包含进程的更多属性或标志,如下:

s This process is a session leader.
+ This process is part of a foreground process group.
These attributes are used for job control.
s 该进程是个会话首进程
+ 该进程是个前台进程(属于前台进程组,一个会话只有一个前台进程组)
这些属性在作用控制中都会用到。

Jobs and sessions 作业控制和会话管理

Job control is what happens when you press ^Z to suspend a program, or when you start a program in the background using &. A job is the same as a process group. Internal shell commands like jobs, fg and bg can be used to manipulate the existing jobs within a session. Each session is managed by a session leader, the shell, which is cooperating tightly with the kernel using a complex protocol of signals and system calls.
    所谓作业(作业中的进程属于同一个进程组)控制,大家平时肯定都用过。比如你按下CTRL+Z(^Z)挂起一个程序,或者使用&使一个程序在后台运行。SHELL也提供相应的作业控制命令(jobs,fg,bg)来操作一个会话中的作业。一个会话是通过会话首进程来管理的,比如SHELL程序一般就是一个会话首进程,它通过复杂的信号机制和系统调用与内核交互。

The following example illustrates the relationship between processes, jobs and sessions:
下面的例子很好的诠释了进程,作业和会话的关系:

The following shell interactions... 下面是一系列通过shell的交互...

...correspond to these processes...这些交互对应一下这些进程...

...and these kernel structures. ...并且对应这些内核数据结构。

  • TTY Driver (/dev/pts/0).
    Size: 45x13
    Controlling process group: (101)
    Foreground process group: (103)
    UART configuration (ignored, 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.
  • pipe0
    Readable end (connected to PID 104 as file descriptor 0)
    Writable end (connected to PID 103 as file descriptor 1)
    Buffer
  • TTY驱动程序(/dev/pts/0)
    大小:45x13
    控制进程组号:(101)
    前台进程组号:(103)
    UAR配置(忽略,因为这是xterm):波特率,奇偶校验,字长等等。
    线路规程配置:规范模式输入处理/非规范模式输入处理,linefeed correction,中断字符的定义等等
    线路规程状态:编辑缓冲区(目前是否为空),光标在缓冲区中的当前位置等等
    管道

  • 可读(作为进程104的标准输入(文件描述符0))
    可写(作为进程103的标准输出(文件描述符1))
    缓冲区

The basic idea is that every pipeline is a job, because every process in a pipeline should be manipulated (stopped, resumed, killed) simultaneously. That's why kill(2) allows you to send signals to entire process groups. By default, fork(2) places a newly created child process in the same process group as its parent, so that e.g. a ^C from the keyboard will affect both parent and child. But the shell, as part of its session leader duties, creates a new process group every time it launches a pipeline.
    首先需要了解到的是,通常SHELL认为组成一个管道的一系列进程(以及其衍生出的进程)是一个作业,因为一个作业中的进程可以同时被控制(停止,继续,杀死)。知道了这点,对于kill(2)可以发送信号给一个进程组就不难理解了。此外,fork(2)默认的将创建的子进程放到与父进程同样的进程组中,所以当我们按下^C(Ctrl+C)后就会发现父进程和子进程都消失了。SHELL通常都是一个会话的首进程,每当我们使用管道建立一个作业时,SHELL都会创建一个新的进程组,作业中的进程全部属于这个新的进程组。

The TTY driver keeps track of the foreground process group id, but only in a passive way. The session leader has to update this information explicitly when necessary. Similarly, the TTY driver keeps track of the size of the connected terminal, but this information has to be updated explicitly, by the terminal emulator or even by the user.
    TTY驱动程序会一直跟踪前台进程组的ID,当然了,TTY驱动程序不会主动获取,而是必要的时候,会话首进程去显示地告诉TTY驱动程序。同样的,TTY驱动程序也会一直跟踪终端的窗口大小,不同的是,不是会话首进程告之的,而是虚拟终端(terminal emulator)或者是用户自己。

As you can see in the diagram above, several processes have /dev/pts/0 attached to their standard input. But only the foreground job (the ls | sort pipeline) will receive input from the TTY. Likewise, only the foreground job will be allowed to write to the TTY device (in the default configuration). If the cat process were to attempt to write to the TTY, the kernel would suspend it using a signal.
    观察上面的图表,我们会发现,大部分进程的标准输入(standard input)是/dev/pts/0。但是事实上,只有前台作业(指的是ls | sort pipeline)可以读取终端上的数据。同样的,也只有前台作业允许往终端写入数据(当然这一切成立的前提是遵循系统默认的配置)。所以我们可以看到,当cat程序试图读取(原文是wirte,我个人觉得不准确,因为cat程序首先读取终端,然后才写终端)终端数据时,内核发送SIGTTIN信号将其挂起。

Signal madness 疯狂的信号

Now let's take a closer look at how the TTY drivers, the line disciplines and the UART drivers in the kernel communicate with the userland processes.
现在让我们进一步了解内核中的TTY驱动程序,线路规程以及UART驱动程序是如何与用户空间的应用程序交流通信的。

UNIX files, including the TTY device file, can of course be read from and written to, and further manipulated by means of the magic ioctl(2) call (the Swiss army-knife of UNIX) for which lots of TTY related operations have been defined. Still, ioctl requests have to be initiated from processes, so they can't be used when the kernel needs to communicate asynchronously with an application.
我们都知道,“一切皆文件”是UNIX的基本哲学之一,这里的文件当然也包括TTY设备文件,应用程序都可以读取和写入数据,更多的操作可以通过神奇的ioctl(2)系统调用来完成(ioctl是UNIX中的瑞士军刀),它定义了很多与TTY相关的操作。但是可惜的是,ioctl依然是应用程序调用的接口,所以内核不可能利用它来异步地同应用程序交流通信。

In The Hitchhiker's Guide to the Galaxy, Douglas Adams mentions an extremely dull planet, inhabited by a bunch of depressed humans and a certain breed of animals with sharp teeth which communicate with the humans by biting them very hard in the thighs. This is strikingly similar to UNIX, in which the kernel communicates with processes by sending paralyzing or deadly signals to them. Processes may intercept some of the signals, and try to adapt to the situation, but most of them don't.
在电影《银河系漫游指南》中,导演Douglas Adams提到过一个非常特别的星球,星球上居住着一群沮丧的人,并且该星球上还有一种长着锋利牙齿的动物,这些动物是通过紧紧的咬人的大腿而与人交流的。这种交流方式和UNIX内核与应用程序通信的方式很相像,内核通过发送信号的方式与应用程序通信,应用程序接收到信号后,也许会做出相应的处理,也许会直接退出或者挂起进入休眠状态。

So a signal is a crude mechanism that allows the kernel to communicate asynchronously with a process. Signals in UNIX aren't clean or general; rather, each signal is unique, and must be studied individually.
信号用来通知进程发生了异步事件。每一个信号在内核中都是独一无二的,我们必须搞清楚每一个信号的功能。

You can use the command kill -l to see which signals your system implements. This is what it may look like:
你可以使用命令kill -l来查看你的系统中实现了哪些信号。就像这样:

As you can see, signals are numbered starting with 1. However, when they are used in bitmasks (e.g. in the output of ps s), the least significant bit corresponds to signal 1.
可以发现,信号编号是从1开始的,但是在信号屏蔽字表示方法中,最低有效为对应信号1。

This article will focus on the following signals: SIGHUP, SIGINT, SIGQUIT, SIGPIPE, SIGCHLD, SIGSTOP, SIGCONT, SIGTSTP, SIGTTIN, SIGTTOU and SIGWINCH.
在本文中,我们主要关注以下几个信号:SIGHUP, SIGINT, SIGQUIT, SIGPIPE, SIGCHLD, SIGSTOP, SIGCONT, SIGTSTP, SIGTTIN, SIGTTOU 和 SIGWINCH.

SIGHUP
默认动作:终止进程
可能的动作:终止进程,忽略该信号,调用相应的信号处理函数
如果终端接口检测到一个连接断开时(调制解调器或网络的断开),UART驱动程序就会发送SIGHUP信号给与该终端相关的控制进程(会话首进程)。通常,该信号会杀死所有的进程,不过有些程序,例如nohup(1)和screen(1),因为与它们的会话分离(控制终端),所以它们的子进程并不会接收到SIGHUP信号。

  根据POSIX.1定义:

  • 挂断信号(SIGHUP)默认的动作是终止程序。
  • 当终端接口检测到网络连接断开,将挂断信号发送给控制进程(会话期首进程)。
  • 如果会话期首进程终止,则该信号发送到该会话期前台进程组。
  • 一个进程退出导致一个孤儿进程组中产生时,如果任意一个孤儿进程组进程处于STOP状态,发送SIGHUP和SIGCONT信号到该进程组中所有进程。

  因此当网络断开或终端窗口关闭后,控制进程收到SIGHUP信号退出,会导致该会话期内其他进程退出。

An example 一个实例

Suppose that you are editing a file in your (terminal based) editor of choice. The cursor is somewhere in the middle of the screen, and the editor is busy executing some processor intensive task, such as a search and replace operation on a large file. Now you press ^Z. Since the line discipline has been configured to intercept this character (^Z is a single byte, with ASCII code 26), you don't have to wait for the editor to complete its task and start reading from the TTY device. Instead, the line discipline subsystem instantly sends SIGTSTP to the foreground process group. This process group contains the editor, as well as any child processes created by it.
假设现在你正在使用你最喜欢的编辑器(基于终端的)编辑一个文件,光标停留在屏幕中央,而此时编辑器正在运行一个很耗CPU的任务,比如在一个很大的文件中查找关键字,或者替换操作。现在你按了下^Z(Ctrl+Z),因为线路规程默认设置(规范模式输入处理)是会截取这个特殊字符的(注意:^Z是一个字符,ASCII码是26),所以你不必等待编辑器执行完当前的任务然后去读取TTY设备,相反的,线路规程会立刻发送SIGTSTP信号给前台进程组,立刻停止前台进程组中的所有进程,我们的编辑器属于前台进程中进程,当然也包括编辑器的子进程,所以我们的编辑器会立刻挂起停止。

The editor has installed a signal handler for SIGTSTP, so the kernel diverts the process into executing the signal handler code. This code moves the cursor to the last line on the screen, by writing the corresponding control sequences to the TTY device. Since the editor is still in the foreground, the control sequences are transmitted as requested. But then the editor sends a SIGSTOP to its own process group.
那么在编辑器接收到SIGTSTP信号到停止这一过程中做了什么呢?一般编辑器在程序中会设置一个信号SIGTSTP的信号处理函数,所以当程序接收到SIGTSTP信号后,内核会转而去运行这个信号处理函数 -- 函数中,会通过向TTY设备写入相应的控制命令来使光标移动到屏幕的最后一行。因为此时编辑器还是前台作业,所以这个控制命令会如实写入TTY设备,接着编辑器发送信号SIGSTOP给自己所在的进程组,到此编辑器及其相关子进程才算真正停止。

The editor has now been stopped. This fact is reported to the session leader using a SIGCHLD signal, which includes the id of the suspended process. When all processes in the foreground job have been suspended, the session leader reads the current configuration from the TTY device, and stores it for later retrieval. The session leader goes on to install itself as the current foreground process group for the TTY using an ioctl call. Then, it prints something like "[1]+ Stopped" to inform the user that a job was just suspended.
编辑器现在处于停止状态。操作系统检测到编辑器状态的改变,从而发送SIGCHLD信号给会话首进程SHELL(会话首进程在这里是编辑器进程的父进程),报告其子进程的信息,这其中就包括被挂起进程的进程ID。当前台进程组中的所有进程都停止运行时,会话首进程SHELL会读取当前TTY设备的配置信息,以备后面恢复作业时用到。接着会话首进程SHELL通过ioctl系统调用使自己成为当前前台进程组(SHELL的提示符会出现)。接着打印出“[1]+ Stopped”信息显示地告诉用户一个作业被挂起了。

At this point, ps(1) will tell you that the editor process is in the stopped state ("T"). If we try to wake it up, either by using the bg built-in shell command, or by using kill(1) to send SIGCONT to the process(es), the editor will start executing its SIGCONT signal handler. This signal handler will probably attempt to redraw the editor GUI by writing to the TTY device. But since the editor is now a background job, the TTY device will not allow it. Instead, the TTY will send SIGTTOU to the editor, stopping it again. This fact will be communicated to the session leader using SIGCHLD, and the shell will once again write "[1]+ Stopped" to the terminal.
此时,通过ps(1)查看可以看到编辑器的状态是停止状态(“T”)。如果你想唤醒编辑器,那么可以使用SHELL内建的命令bg,或者直接通过kill(1)发送SIGCONT信号给编辑器进程,编辑器进程会被唤醒,重新开始从它的SIGCONT信号处理函数中运行。在信号函数中,编辑器也许会试图通过写TTY设备来重绘画面,但是别忘了,因为编辑器进程此时是个后台进程,而后台进程是不允许往控制终端(TTY设备)写入任何数据的,所以终端驱动程序会发送信号SIGTTOU信号给编辑器,该信号会暂时停止编辑器进程。同样的,编辑器进程状态的改变会引起内核发送SIGCHLD信号给会话首进程,所以SHELL会再次输出“[1]+ Stopped”信息到终端上。

When we type fg, however, the shell first restores the line discipline configuration that was saved earlier. It informs the TTY driver that the editor job should be treated as the foreground job from now on. And finally, it sends a SIGCONT signal to the process group. The editor process attempts to redraw its GUI, and this time it will not be interrupted by SIGTTOU since it is now a part of the foreground job.
当我们输入fg命令后,SHELL首先会恢复之前保存的TTY设备信息,通知TTY设备从现在开始编辑器进程是前台作业了。接着,SHELL发送信号SIGCONT给编辑器进程所在的进程组。接收信号后,编辑器进程试图重绘画面,而这次就不会被信号SIGTTOU阻止了,因为它已经是前台作业中的一个进程了。

Flow control and blocking I/O 流控制 和 阻塞的I/O

Run yes in an xterm, and you will see a lot of "y" lines swooshing past your eyes. Naturally, the yes process is able to generate "y" lines much faster than the xterm application is able to parse them, update its frame buffer, communicate with the X server in order to scroll the window and so on. How is it possible for these programs to cooperate?
当我们在xtern上运行yes命令时,会看到一连串的“y”嗖的一声在我们眼前划过。但是我们知道,xterm一般需要先读取这些数据,然后更新自己的帧缓冲区,最后需要与图形服务通信做出相应的操作,例如翻页操作等等,但是这一系列过程的速度肯定是低于yes程序往其写的速度的,那么它们是如何配合而完成这一切操作的呢?

The answer lies in blocking I/O. The pseudo terminal can only keep a certain amount of data inside its kernel buffer, and when that buffer is full and yes tries to call write(2), then write(2) will block, moving the yes process into the interruptible sleep state where it remains until the xterm process has had a chance to read off some of the buffered bytes.
答案就是依靠阻塞的I/O。伪终端一般只会在内核缓冲区中保留固定大小的数据,当数据满时,内核就会调用系统调用wirte(2),由于要一次性写完这么多数据,所以write(2)会阻塞在那里,与此同时,yes进程也被内核设置为睡眠状态(可中断睡眠状态)。这种状态会一直持续到xterm进程已经可以读取缓冲区中的数据了为止。

The same thing happens if the TTY is connected to a serial port. yes would be able to transmit data at a much higher rate than, say, 9600 baud, but if the serial port is limited to that speed, the kernel buffer soon fills up and any subsequent write(2) calls block the process (or fail with the error code EAGAIN if the process has requested non-blocking I/O).
当TTY与一个串口相连时,道理也是一样的。比如这个串口的最大波特率是9600波特,但是你向其写入数据的速度可能会远远大于这个速度,那么内核缓冲区会很快被填满,所以后续的系统调用write(2)会阻塞我们的程序(如果我们程序中设置的是非阻塞I/O模式的话,那么write会直接返回错误码EAGAIN而返回),以保证数据正常的显示在串口上。

What if I told you, that it is possible to explicitly put the TTY in a blocking state even though there is space left in the kernel buffer? That until further notice, every process trying to write(2) to the TTY automatically blocks. What would be the use of such a feature?
如果我现在告诉你,尽管有时候内核缓冲区没有满,但是也有可能显示地设置TTY为阻塞模式的话,你会不会觉得很奇怪?也就是说,在这种情况下,任何调用write(2)的程序都会被自动阻塞。这个特殊的特性有什么用处呢?

Suppose we're talking to some old VT-100 hardware at 9600 baud. We just sent a complex control sequence asking the terminal to scroll the display. At this point, the terminal gets so bogged down with the scrolling operation, that it's unable to receive new data at the full rate of 9600 baud. Well, physically, the terminal UART still runs at 9600 baud, but there won't be enough buffer space in the terminal to keep a backlog of received characters. This is when it would be a good time to put the TTY in a blocking state. But how do we do that from the terminal?
假设我们正在使用一个波特率为9600的老式VT-100终端设备上工作,并且发送了一个复杂的控制命令给它,让它执行翻页操作,我们会发现终端会处于接近“崩溃”状态非常得卡顿,那么这个时候终端是不可能在波特率为9600的情况下接收新的数据的(此时终端的速度应该低于9600)。然而,UART驱动程序此时的速率依然是9600,那么终端的缓冲区(buffer space in the terminal)很快就会满,根本没有足够的空间来存储大量的数据,所以如果这个时候能将TTY设置为阻塞状态的话,不就解决了这一难题了吗? 现在的问题就是如何设置的问题了!

We have already seen that a TTY device may be configured to give certain data bytes a special treatment. In the default configuration, for instance, a received ^C byte won't be handed off to the application through read(2), but will instead cause a SIGINT to be delivered to the foreground job. In a similar way, it is possible to configure the TTY to react on a stop flow byte and a start flow byte. These are typically ^S (ASCII code 19) and ^Q (ASCII code 17) respectively. Old hardware terminals transmit these bytes automatically, and expect the operating system to regulate its flow of data accordingly. This is called flow control, and it's the reason why your xterm sometimes appears to lock up when you accidentally press ^S.
我们已经了解了TTY设备可以设置成规范模式,在这种模式下会对于特殊的输入字符做特殊的处理。举例来说,TTY接收到^C(Ctrl+C)时,并不是将该字符直接传递给应用程序(应用程序通过read(2)来读取),而是发送一个SIGINT信号给前台作业。同样的,可以发送特殊的字符给TTY设备,使其可以停止接收字节流或者开始接收字节流。实际上,一般是通过特殊字符^S(Ctrl+S)和^Q(Ctrl+Q)来分别实现这两种功能的。老式的终端设备直接将这两个字符传递给操作系统,让操作系统来相应的控制字节流。这就是流控制的概念,并且这也就能够解释当你无意按了^S(Ctrl+S)后,为什么你的xterm好像被锁住了一样的原因了。

There's an important difference here: Writing to a TTY which is stopped due to flow control, or due to lack of kernel buffer space, will block your process, whereas writing to a TTY from a background job will cause a SIGTTOU to suspend the entire process group. I don't know why the designers of UNIX had to go all the way to invent SIGTTOU and SIGTTIN instead of relying on blocking I/O, but my best guess is that the TTY driver, being in charge of job control, was designed to monitor and manipulate whole jobs; never the individual processes within them.
这里需要我们弄清楚的是:由于流控制而把TTY设置成阻塞模式导致的进程阻塞,和由于内核缓冲区已经满了而导致的进程阻塞是不一样的。当后台作业试图往终端写数据时,终端驱动程序会发送信号SIGTTOU给进程而使其停止运行。我一直不明白为什么UNIX的设计者一致同意使用信号SIGTTOU和SITTIN而不采用阻塞的I/O来停止进程。我想这其中的原因也许是因为终端驱动程序管理者作业控制,它监视和操作的是整个作业,而绝不是作业中的某一单个进程吧。

Configuring the TTY device 配置TTY设备
To find out what the controlling TTY for your shell is called, you could refer to the ps l listing as described earlier, or you could simply run the tty(1) command.
想知道你的SHELL的控制终端(controlling TTY)是什么,你可以通过之前的ps l命令来查看,或者直接通过tty(1)来获得。

A process may read or modify the configuration of an open TTY device using ioctl(2). The API is described in tty_ioctl(4). Since it's part of the binary interface between Linux applications and the kernel, it will remain stable across Linux versions. However, the interface is non-portable, and applications should rather use the POSIX wrappers described in the termios(3) man page.
我们可以通过ioctl来读取或者设置修改一个TTY设备的配置属性。相关的API可以参看tty_ioctl(4)。因为,这是应用程序和内核的二进制接口的一部分,所以他们在不同的linux版本中一直保持稳定不变。但他们是不可移植的,所以推荐应用程序调用POSIX封装的接口(参考termios(3)man文档)。

I won't go into the details of the termios(3) interface, but if you're writing a C program and would like to intercept ^C before it becomes a SIGINT, disable line editing or character echoing, change the baud rate of a serial port, turn off flow control etc. then you will find what you need in the aforementioned man page.
在这里,我不打算详谈termios(3)接口,但如果你是C程序员,你想在^C(Ctrl+C)被换成信号SIGINT之前截取该字符,又或者你想关闭行编辑功能,关闭回显功能,改变串口的波特率,关闭流控制等等的话,那么你就的仔细阅读termios(3)man文档了。

There is also a command line tool, called stty(1), to manipulate TTY devices. It uses the termios(3) API.
系统同样提供了stty(1)命令来让我们操作TTY设备,它就是用termios(3)API实现的。
Let's try it!
让我们试一试吧!

The -a flag tells stty to display all settings. By default, it will look at the TTY device attached to your shell, but you can specify another device with -F.
参数-a说明罗列出所有的设置。默认的,stty为呈现当前SHELL的控制终端的的属性设置,但是你也可以通过-F参数指定其他的终端设备。

Some of these settings refer to UART parameters, some affect the line discipline and some are for job control. All mixed up in a bucket for monsieur. Let's have a look at the first line:
其中的有些设置属性是与UART相关的,有的与线路规程相关,有的与作业控制相关。这些属性全部糅合在一起了。我们来看第一行:

Attribute Related part Description
speed   UART          The baud rate. Ignored for pseudo terminals.
rows,columns                        TTY driver Somebody's idea of the size, in characters, of the terminal attached to this TTY device. Basically, it's just a pair of variables within kernel space, that you may freely set and get. Setting them will cause the TTY driver to dispatch a SIGWINCH to the foreground job.
line Line discipline                 The line discipline attached to the TTY device. 0 is N_TTY. Listed in /proc/tty/ldiscs.

Try the following: Start an xterm. Make a note of its TTY device (as reported by tty) and its size (as reported by stty -a). Start vim (or some other full-screen terminal application) in the xterm. The editor queries the TTY device for the current terminal size in order to fill the entire window. Now, from a different shell window, type:
我们可以做一个实验:开启一个xterm,获得它的终端设备的名称(通过tty(1)程序获得)和窗口大小(通过stty -a获得),然后运行vim程序(也可以是其他默认使用全屏的终端应用程序)。vim编辑器首先会获取终端设备的当前窗口大小属性,并依此填充整个窗口。现在我们从另一个SHELL窗口,运行如下命令:
stty -F X rows Y

where X is the TTY device, and Y is half the terminal height. This will update the TTY data structure in kernel memory, and send a SIGWINCH to the editor, which will promptly redraw itself using only the upper half of the available window area.
X是你的终端名称,Y是你当前终端高度的一半大小数值。这会更新内核内存中的与终端设备相关的数据结构,并且内核会给编辑器发送SGIWINCH信号,那么vim会迅速的使用这新新值来重绘窗口区域。

The second line of stty -a output lists all the special characters. Start a new xterm and try this:
从stty -a输出的第二行我们看到罗列的是系统定义的特殊字符。开启一个新的xterm,并输入以下命令:
stty intr o

Now "o", rather than ^C, will send a SIGINT to the foreground job. Try starting something, such as cat, and verify that you can't kill it using ^C. Then, try typing "hello" into it.
现在你会发现,发送信号SIGINT给前台作业的是字符“o”了,而不再是^C(Ctrl+C)。接着运行cat程序,你会发现^C已经不能杀死进程了,相反的,当我们试图驶入“hello”字符串是,遇到“o”cat就退出了。

Occasionally, you may come across a UNIX system where the backspace key doesn't work. This happens when the terminal emulator transmits a backspace code (either ASCII 8 or ASCII 127) which doesn't match the erase setting in the TTY device. To remedy the problem, one usually types stty erase ^H (for ASCII 8) or stty erase ^? (for ASCII 127). But please remember that many terminal applications use readline, which puts the line discipline in raw mode. Those applications aren't affected.
也许你曾遇到过这样的窘境,你的UNIX系统退格键不起作用了,这是因为你发送的退格键控制码(ASCII编号8或者127)与TTY设备的默认设置不一致导致的。修复这个问题,可以通过设置stty erase ^H (for ASCII 8)或者stty erase ^? (for ASCII 127)来修复。但是,别忘了,有很多终端应用程序使用了readline库,而readline库是把线路规程设置成非规范模式的,所以这些设置对这些程序根本没有影响。

Finally, stty -a lists a bunch of switches. As expected, they are listed in no particular order. Some of them are UART-related, some affect the line discipline behaviour, some are for flow control and some are for job control. A dash (-) indicates that the switch is off; otherwise it is on. All of the switches are explained in the stty(1) man page, so I'll just briefly mention a few:
最后,stty -a罗列了很多的功能开关。正如你见到的,它们的输出排列是不分先后的。同样的,它们有的与UART相关,有的与线路规程相关,有的与流控制相关,与的则与作业控制相关。破折号(-)表明该功能已被禁止,相反,没有破折号(-)的则说明该功能已经开启。所有功能的详细描述可参数tty(1)的man文档。所以这里我简略的介绍介个:
icanon toggles the canonical (line-based) mode. Try this in a new xterm:
icanon标志用来设置终端的模式,在一个新的xterm中运行如下命令:
stty -icanon; cat

Note how all the line editing characters, such as backspace and ^U, have stopped working. Also note that cat is receiving (and consequently outputting) one character at a time, rather than one line at a time.
你会发现很多特殊字符已经没有作用了,例如退格键和^U(Ctrl+U删除整行)等。并且cat程序现在一次只读取一个字符(一次输出也是一个字符),而不是以前的整行了。

echo enables character echoing, and is on by default. Re-enable canonical mode (stty icanon), and then try:
echo标志用来设置回显功能,并且系统默认是设置回显功能的。重新设置终端为规范模式(canonical mode)后,在输入以下命令:
stty -echo; cat
As you type, your terminal emulator transmits information to the kernel. Usually, the kernel echoes the same information back to the terminal emulator, allowing you to see what you type. Without character echoing, you can't see what you type, but we're in cooked mode so the line editing facilities are still working. Once you press enter, the line discipline will transmit the edit buffer to cat, which will reveal what your wrote.
当你输入字符的时候,你的虚拟终端会将这些信息传递给内核。通常,内核会回显这些信息在你的虚拟终端上,让你实时地看见你输入的信息。但是没了回显功能,你就不能看到这些信息了,可是因为我们处在规范模式下,所以行编辑功能依然有效,所有一旦你回车,线路规程就会将编辑缓冲区中的信息传递给cat程序,这时我们就能看到我们输入的是什么了。

tostop controls whether background jobs are allowed to write to the terminal. First try this:
tostop标志用来控制后台作业是否可以往终端写入数据。我们的实验如下,先输入这些:
stty tostop; (sleep 5; echo hello, world) &

The & causes the command to run as a background job. After five seconds, the job will attempt to write to the TTY. The TTY driver will suspend it using SIGTTOU, and your shell will probably report this fact, either immediately, or when it's about to issue a new prompt to you. Now kill the background job, and try the following instead:
符号&使这个命令作为后端作业。5秒后,这个作业试图往终端写入数据,然后TTY驱动程序会发送信号SIGTTOU给作业并挂起作业,你的SHELL也许会向你报告这个事实(我的bash下是键入回车后才会提示我),也许只是迅速的打印出一个新的命令提示符给你。现在kill掉这个后台作业,并运行下面的命令:
stty -tostop; (sleep 5; echo hello, world) &

You will get your prompt back, but after five seconds, the background job transmits hello, world to the terminal, in the middle of whatever you were typing.
运行的结果是,你会立刻看到SHELL打印出命令提示符。但是5秒后,不管你正在输入什么,这个后台作业都会在终端上打印出“hello, world”字符串。

Finally, stty sane will restore your TTY device configuration to something reasonable.
最后,通过stty sane命令可以恢复TTY设备的所有默认配置。

Conclusion 大总结

I hope this article has provided you with enough information to get to terms with TTY drivers and line disciplines, and how they are related to terminals, line editing and job control. Further details can be found in the various man pages I've mentioned, as well as in the glibc manual (info libc, "Job Control").
我希望本文可以让你很好的理解TTY驱动程序和线路规程的作用以及它们与终端,行编辑和作业控制的关系。更详细的讨论读者可以参看本文推荐的相关man文档,和glibc官方文档(info libc, "Job Control")。

Finally, while I don't have enough time to answer all the questions I get, I do welcome feedback on this and other pages on the site. Thanks for reading!
最后,尽管我没有足够的时间来回答提出的所有问题,但是我会尽量回复你们,谢谢阅读!

参考链接:

《彻底理解Linux的各种终端类型以及概念》

《Linux終端和Line discipline圖解》

《Terminals》《CORE TECHNOLOGY: SIGNALS》《TTY》

《你真的知道什么是终端吗?》

《Linux运行与控制后台进程的方法:nohup, setsid, &, disown, screen》

《 对于Linux内核tty设备的一点理解 》

《TTY的那些事儿》

《进程管理和终端驱动:基本概念》

《Linux TTY framework》

《Linux Virtual Console》

《进程管理和终端驱动:基本概念》

《Why the TTY line discipline exists in the kernel》

《[术语] 什 line discipline (线路规程)》

《Linux Serial Console HOWTO》

《Understanding UNIX termios VMIN and VTIME》

《tty设备驱动程序设计》

《再谈UNIX流机制和tty驱动》

《A Brief Introduction to Termios》

《Using pseudo-terminals (pty) to control interactive programs》

《how to open, read, and write from serial port in C》

《Linux的控制台(TTY/PTY)与多任务(MULTI-TASKING》

《 线路规程(line disciplines)》

《Linux终端(一)》

《Linux TTY/PTS概述》《Linux session和进程组概述》

《Linux 的伪终端的基本原理 及其在远程登录(SSH,telnet等)中的应用》

《什么是 Terminal》

《What are the responsibilities of each Pseudo-Terminal (PTY) component (software, master side, slave side)?》

《linux的终端,网络虚拟终端,伪终端 》

《第 34 章 终端、作业控制与守护进程》

《what is the difference between tty and vty in linux》

《Linux 伪终端的基本原理 及其在远程登录(SSH,telnet等)中的应用》(https://www.cnblogs.com/zzdyyy/p/7538077.html)

《终端tty、虚拟控制台、FrameBuffer的切换过程详解》

《终端的前世今生》

《In UNIX programming, what is the "controlling terminal"? What is the intuition behind having background and foreground process groups?》

《Why does reading from two connected pty's cause an infinite loop?》《解密TTY》

《Console-terminal-tty-shell-kernel》

《Linux 进程组(process group)和会话(session)》

《[리눅스커널][시그널] 시그널 생성: 커널은 언제 시그널 생성할까?》

《命令行界面 (CLI)、终端 (Terminal)、Shell、TTY,傻傻分不清楚?》

《特集 : 作ってみよう,MyガジェットPart3》

《How does Ctrl-C terminate a child process?》

《Virtual creatures and their habitats: the past and present TTY in Linux》

《Upgrading simple shells to fully interactive TTYs》《suspended(tty input)》

《Get rid of SIGINT》

TTY解密(The TTY demystified)相关推荐

  1. 神秘TTY:The TTY demystified

    目录 History The use cases Processes Jobs and sessions The following shell interactions... ...correspo ...

  2. linux下进程的tty,Linux下TTY驱动程序分析

    3.核心结构体 #include struct tty_driver { int magic;  /*幻数,通常被设置为TTY_DRIVER_MAGIC.在alloc_tty_driver函数中被初始 ...

  3. linux TTY子系统(3) - tty driver

    了解linux tty driver 1.TTY device   kernel从设备模型和字符设备两个角度对它进行了抽象: 设备模型的角度   为每个"数据通道"注册了一个stu ...

  4. linux 什么是tty console和tty和串口的关系 如何在linux下查看tty对应哪个串口

    什么是TTY https://blog.csdn.net/goooooooooo/article/details/1302301?ops_request_misc=%257B%2522request% ...

  5. linux tty字体,ArchLinux TTY 中文字体渲染

    ArchLinux 的 User Centrality 原则中提出, 该发行版意图满足贡献者的需求,而不是吸引尽量多的用户. 正是这一原则使得 ArchLinux 吸引了大量的开发者, 其 AUR 也 ...

  6. linux开机自启动tty设备,在 tty 里添加一个开机自启动的任务管理器

    每当感觉到系统卡的时候,最好的方法无外乎进入 tty,登入,打开一个 top 监视.可是每次到了需要的时候才去开,打开的效率自然不敢恭维.于是便想,每次开机的时候,记起来就跑到 tty 下面去先开起来 ...

  7. Linux TTY/PTS概述

    https://segmentfault.com/a/1190000009082089 Table of Contents TTY历史 支持多任务的计算机出现之前 支持多任务的计算机出现之后 内核TT ...

  8. 一文彻底讲清Linux tty子系统架构及编程实例

    [摘要]本文详细解读了linux系统下的tty子系统的深层次原理和架构,并参考了LDD3中的代码实例讲述了无硬件下实现一个简单的tty设备驱动模块的编写.对了解tty子系统及下部串口驱动模块的学习有较 ...

  9. 浅谈Linux tty体系,理清tty驱动层次与各种概念

    虽然Linux内核是由C语言写的,但处处体现面向对象的设计思想,这对很多只会C语言的朋友来说,理解比较困难,尤其是tty体系,涉及很多混乱的概念. 1. 上古时期的tty.terminal和conso ...

最新文章

  1. php excel类 ,phpExcel使用方法介绍
  2. rails小重构:将图片加入产品Model
  3. RANK() OVER(PARTITION BY deptno ORDER BY empno)
  4. rpm安装mysql报错NOKEY_rpm包安装报错: Header V3 RSASHA256 Signature, key ID fd431d51 NOKEY
  5. 安装python3.6-pyppeteer
  6. 大数据笔记(六)——HDFS的底层原理:JAVA动态代理和RPC
  7. go操作网页元素_7天用Go动手写/从零实现分布式缓存GeeCache
  8. Oracle如何监控表的DML次数
  9. 电脑主机,晚上就煎肉,把隔壁宿舍都馋哭了!
  10. WinCE6下的kernelIoControl使用方法
  11. 阿里最快数周内提交赴港上市申请?回应:不予置评
  12. 汽车经销商销售发票扫描识别方案
  13. Effective C++ item 6
  14. 2018的趋势与展望(上)——记罗振宇“时间的朋友2017”跨年演讲
  15. 产品碳足迹ISO14067认证
  16. 硕士毕业论文讨论部分怎么写啊?
  17. mac上Latex的安装及使用教程
  18. aicloud服务器不稳定,华硕AC级无线路由器搭载AiCloud上市
  19. 商圈分析如何大数据软件采集相关要素
  20. 也说 “EMC” 的”邮件门”事件

热门文章

  1. 敏感词汇过滤器(过滤器技术)
  2. 硬件设备二 调试分类、软/硬件断点、OpenOCD、JLink、STLink 使用
  3. Java中使用发布订阅模式
  4. 我的世界怎么用计算机,我的世界计算器怎么用 全计算器使用说明
  5. Jrebel激活服务器
  6. 天道酬勤、地道酬善、人道酬诚、商道酬信、业道酬精
  7. NEXYS4_DDR迪芝伦XC7A100TCSG324-1型,点亮板载的8个8位数码管
  8. linux文件属性及 ls -l 命令输出结果详解
  9. 如何让聊天机器人懂情感?这是一篇来自清华的论文
  10. 内推 | 【叫叫-数据分析师】成都 15-25K