Real teletypes in the 1940s.
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 findgood introductory articles about it. I believe that a basic understanding ofTTYs in Linux is essential for the developer and the advanced user.
Beware, though: What you are about to see is not particularly elegant. Infact, the TTY subsystem — while quite functional from a user's point ofview — is a twisty little mess of special cases. To understand how thiscame to be, we have to go back in time.
In 1869, the stock ticker was invented. It was an electro-mechanicalmachine consisting of a typewriter, a long pair of wires and a ticker tapeprinter, and its purpose was to distribute stock prices over long distances inrealtime. This concept gradually evolved into the faster, ASCII-basedteletype. Teletypes were once connected across the world in a largenetwork, called Telex, which was used for transferring commercialtelegrams, but the teletypes weren't connected to any computers yet.
Meanwhile, however, the computers — still quite large andprimitive, but able to multitask — were becoming powerful enough to beable to interact with users in realtime. When the command line eventuallyreplaced the old batch processing model, teletypes were used as input andoutput devices, because they were readily available on the market.
There was a plethora of teletype models around, all slightly different, sosome 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-leveldetails, such as word length, baud rate, flow control, parity, control codesfor rudimentary line editing and so on. Fancy cursor movements, colour outputand other advanced features made possible in the late 1970s by solid statevideo terminals such as the VT-100, were left to the applications.
In present time, we find ourselves in a world where physical teletypes andvideo terminals are practically extinct. Unless you visit a museum or ahardware enthusiast, all the TTYs you're likely to see will be emulated videoterminals — software simulations of the real thing. But as we shallsee, the legacy from the old cast-iron beasts is still lurking beneath thesurface.
A user types at a terminal (a physical teletype). This terminal is connectedthrough a pair of wires to a UART (Universal Asynchronous Receiver andTransmitter) on the computer. The operating system contains a UART driver whichmanages the physical transmission of bytes, including parity checks and flowcontrol. In a na?ve system, the UART driver would then deliver the incomingbytes directly to some application process. But such an approach would lack thefollowing essential features:
Line editing. Most users make mistakes while typing, so a backspacekey is often useful. This could of course be implemented by the applicationsthemselves, but in accordance with the UNIX design philosophy, applicationsshould be kept as simple as possible. So as a convenience, the operating systemprovides an editing buffer and some rudimentary editing commands (backspace,erase word, clear line, reprint), which are enabled by default inside theline discipline. Advanced applications may disable these features byputting the line discipline in raw mode instead of the defaultcooked (or canonical) mode. Most interactive applications(editors, mail user agents, shells, all programs relying on curses orreadline) run in raw mode, and handle all the line editing commandsthemselves. The line discipline also contains options for character echoing andautomatic conversion between carriage returns and linefeeds. Think of it as aprimitive kernel-level sed(1), if you like.
Incidentally, the kernel provides several different line disciplines. Onlyone of them is attached to a given serial device at a time. The defaultdiscipline, which provides line editing, is called N_TTY(drivers/char/n_tty.c, if you're feeling adventurous). Otherdisciplines are used for other purposes, such as managing packet switched data(ppp, IrDA, serial mice), but that is outside the scope of this article.
Session management. The user probably wants to run several programssimultaneously, and interact with them one at a time. If a program goes into anendless loop, the user may want to kill it or suspend it. Programs that arestarted in the background should be able to execute until they try to write tothe terminal, at which point they should be suspended. Likewise, user inputshould be directed to the foreground program only. The operating systemimplements these features in the TTY driver(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 orientedterminology, the TTY driver is a passive object. It has some data fields and somemethods, but the only way it can actually do something is when one of itsmethods gets called from the context of a process or a kernel interrupthandler. The line discipline is likewise a passive entity.
Together, a particular triplet of UART driver, line discipline instance andTTY driver may be referred to as a TTY device, or sometimes just TTY. Auser process can affect the behaviour of any TTY device by manipulating thecorresponding device file under /dev. Write permissions to the devicefile are required, so when a user logs in on a particular TTY, that user mustbecome the owner of the device file. This is traditionally done by thelogin(1) program, which runs with root privileges.
The physical line in the previous diagram could of course be a long-distancephone line:
This does not change much, except that the system now has to handle a modemhangup situation as well.
Let's move on to a typical desktop system. This is how the Linux consoleworks:
The TTY driver and line discipline behave just like in the previousexamples, but there is no UART or physical terminal involved anymore. Instead,a video terminal (a complex state machine including a frame buffer ofcharacters and graphical character attributes) is emulated in software, andrendered to a VGA display.
The console subsystem is somewhat rigid. Things get more flexible (andabstract) if we move the terminal emulation into userland. This is howxterm(1) and its clones work:
To facilitate moving the terminal emulation into userland, while stillkeeping the TTY subsystem (session management and line discipline) intact, thepseudo terminal or pty was invented. And as you may have guessed,things get even more complicated when you start running pseudo terminalsinside pseudo terminals, à la screen(1) or ssh(1).
Now let's take a step back and see how all of this fits into theprocess model.
A Linux process can be in one of the following states:
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. |
By running ps l, you can see which processes are running, and which aresleeping. If a process is sleeping, the WCHAN column ("wait channel", the nameof the wait queue) will tell you what kernel event the process is waiting for.
$ ps lF UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND0 500 5942 5928 15 0 12916 1460 wait Ss pts/14 0:00 -/bin/bash0 500 12235 5942 15 0 21004 3572 wait S+ pts/14 0:01 vim index.php0 500 12580 12235 15 0 8080 1440 wait S+ pts/14 0:00 /bin/bash -c (ps l) >/tmp/v727757/1 2>&10 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 theirchild processes. There are two sleeping states: Interruptiblesleep and uninterruptible sleep. Interruptible sleep (the most common case)means that while the process is part of a wait queue, it may actually also bemoved to the running state when a signal is sent to it. If you look inside thekernel source code, you will find that any kernel code which is waiting for anevent must check if a signal is pending after schedule() returns, and abort thesyscall in that case.
In the ps listing above, the STAT column displays the current state of eachprocess. The same column may also contain one or more attributes, or flags:
s | This process is a session leader. |
+ | This process is part of a foreground process group. |
These attributes are used for job control.
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 isthe same as a process group. Internal shell commands like jobs,fg and bg can be used to manipulate the existing jobs withina session. Each session is managed by a session leader, theshell, which is cooperating tightly with the kernel using a complex protocol ofsignals and system calls.
The following example illustrates the relationship between processes, jobsand sessions:
Size: 45x13Controlling 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.
Readable end (connected to PID 104 as file descriptor 0)Writable end (connected to PID 103 as file descriptor 1)Buffer
The basic idea is that every pipeline is a job, because every process in apipeline should be manipulated (stopped, resumed, killed) simultaneously.That's why kill(2) allows you to send signals to entire processgroups. By default, fork(2) places a newly created child process inthe same process group as its parent, so that e.g. a ^C from thekeyboard will affect both parent and child. But the shell, as part of itssession leader duties, creates a new process group every time itlaunches a pipeline.
The TTY driver keeps track of the foreground process group id, but only in apassive way. The session leader has to update this information explicitly whennecessary. Similarly, the TTY driver keeps track of the size of the connectedterminal, but this information has to be updated explicitly, by the terminalemulator or even by the user.
As you can see in the diagram above, several processes have/dev/pts/0 attached to their standard input. But only the foregroundjob (the ls | sort pipeline) will receive input from theTTY. Likewise, only the foreground job will be allowed to write to the TTYdevice (in the default configuration). If the cat process were to attempt towrite to the TTY, the kernel would suspend it using a signal.
Now let's take a closer look at how the TTY drivers, the line disciplinesand the UART drivers in the kernel communicate with the userland processes.
UNIX files, including the TTY device file, can of course be read from andwritten to, and further manipulated by means of the magic ioctl(2) call (the Swissarmy-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 beused when the kernel needs to communicate asynchronously with anapplication.
In The Hitchhiker's Guide to the Galaxy, Douglas Adams mentions anextremely dull planet, inhabited by a bunch of depressed humans and a certain breedof animals with sharp teeth which communicate with the humans by biting themvery hard in the thighs. This is strikingly similar to UNIX, in which the kernelcommunicates 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.
So a signal is a crude mechanism that allows the kernel to communicateasynchronously 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 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR213) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+439) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+843) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+1247) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-1451) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-1055) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-659) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-263) SIGRTMAX-1 64) SIGRTMAX
As you can see, signals are numbered starting with 1. However, when they areused in bitmasks (e.g. in the output of ps s), the least significant bitcorresponds to signal 1.
This article will focus on the following signals: SIGHUP,SIGINT, SIGQUIT, SIGPIPE, SIGCHLD,SIGSTOP, SIGCONT, SIGTSTP, SIGTTIN,SIGTTOU and SIGWINCH.
SIGHUP is sent by the UART driver to the entire session when a hangupcondition has been detected. Normally, this will kill all the processes. Someprograms, such as nohup(1) and screen(1), detachfrom their session (and TTY), so that their child processes won't notice ahangup.
SIGINT is sent by the TTY driver to the current foreground job when theinteractive attention character (typically ^C, which has ASCIIcode 3) appears in the input stream, unless this behaviour has been turnedoff. Anybody with access permissions to the TTY device can change theinteractive attention character and toggle this feature; additionally, thesession manager keeps track of the TTY configuration of each job, and updatesthe TTY whenever there is a job switch.
SIGQUIT works just like SIGINT, but the quit character is typically^\ and the default action is different.
The kernel sends SIGPIPE to any process which tries to write to a pipe withno readers. This is useful, because otherwise jobs likeyes | head would never terminate.
When a process dies or changes state (stop/continue), the kernel sends aSIGCHLD to its parent process. The SIGCHLD signal carries additionalinformation, namely the process id, the user id, the exit status (ortermination signal) of the terminated process and some execution timestatistics. The session leader (shell) keeps track of its jobs using thissignal.
This signal will unconditionally suspend the recipient, i.e. its signalaction can't be reconfigured. Please note, however, that SIGSTOPisn't sent by the kernel during job control. Instead, ^Z typicallytriggers a SIGTSTP, which can be intercepted by the application. Theapplication may then e.g. move the cursor to the bottom of the screen orotherwise put the terminal in a known state, and subsequently put itself to sleep using SIGSTOP.
SIGCONT will un-suspend a stopped process. It is sent explicitly by theshell when the user invokes the fg command. Since SIGSTOP can't beintercepted by an application, an unexpected SIGCONT signal might indicate thatthe process was suspended some time ago, and then un-suspended.
SIGTSTP works just like SIGINT and SIGQUIT, but the magic character istypically ^Z and the default action is to suspend the process.
If a process within a background job tries to read from a TTY device, theTTY sends a SIGTTIN signal to the entire job. This will normally suspend thejob.
If a process within a background job tries to write to a TTY device, the TTYsends a SIGTTOU signal to the entire job. This will normally suspend the job.It is possible to turn off this feature on a per-TTY basis.
As mentioned, the TTY device keeps track of the terminal size, but thisinformation needs to be updated manually. Whenever that happens, the TTY devicesends SIGWINCH to the foreground job. Well-behaving interactive applications,such as editors, react upon this, fetch the new terminal size from the TTYdevice and redraw themselves accordingly.
Suppose that you are editing a file in your (terminal based) editor ofchoice. The cursor is somewhere in the middle of the screen, and the editor isbusy executing some processor intensive task, such as a search and replaceoperation on a large file. Now you press ^Z. Since the linediscipline has been configured to intercept this character (^Z is asingle byte, with ASCII code 26), you don't have to wait for the editor tocomplete its task and start reading from the TTY device. Instead, the line disciplinesubsystem instantly sends SIGTSTP to the foreground process group. This processgroup contains the editor, as well as any child processes created by it.
The editor has installed a signal handler for SIGTSTP, so the kernel divertsthe process into executing the signal handler code. This code moves the cursorto the last line on the screen, by writing the corresponding control sequencesto the TTY device. Since the editor is still in the foreground, the controlsequences are transmitted as requested. But then the editor sends a SIGSTOP toits own process group.
The editor has now been stopped. This fact is reported to the session leaderusing a SIGCHLD signal, which includes the id of the suspendedprocess. When all processes in the foreground job have been suspended, thesession leader reads the current configuration from the TTY device, and storesit for later retrieval. The session leader goes on to install itself as thecurrent foreground process group for the TTY using an ioctl call.Then, it prints something like "[1]+ Stopped" to inform the user that a job wasjust suspended.
At this point, ps(1) will tell you that the editor process is inthe stopped state ("T"). If we try to wake it up, either by using the bgbuilt-in shell command, or by using kill(1) to send SIGCONT to theprocess(es), the editor will start executing its SIGCONT signal handler. Thissignal handler will probably attempt to redraw the editor GUI by writing to theTTY device. But since the editor is now a background job, the TTY device willnot allow it. Instead, the TTY will send SIGTTOU to the editor, stopping itagain. This fact will be communicated to the session leader using SIGCHLD, andthe shell will once again write "[1]+ Stopped" to the terminal.
When we type fg, however, the shell first restores the linediscipline configuration that was saved earlier. It informs the TTY driver thatthe editor job should be treated as the foreground job from now on. Andfinally, it sends a SIGCONT signal to the process group. The editor processattempts to redraw its GUI, and this time it will not be interrupted by SIGTTOUsince it is now a part of the foreground job.
Run yes in an xterm, and you will see a lot of"y" lines swooshing past your eyes. Naturally, the yesprocess is able to generate "y" lines much faster than thexterm 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 isit possible for these programs to cooperate?
The answer lies in blocking I/O. The pseudo terminal can only keep acertain amount of data inside its kernel buffer, and when that buffer is fulland yes tries to call write(2), then write(2) willblock, moving the yes process into the interruptible sleepstate where it remains until the xterm process has had a chance toread off some of the buffered bytes.
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 kernelbuffer soon fills up and any subsequent write(2) calls block theprocess (or fail with the error code EAGAIN if the process hasrequested non-blocking I/O).
What if I told you, that it is possible to explicitly put the TTY in ablocking state even though there is space left in the kernel buffer? That untilfurther notice, every process trying to write(2) to the TTYautomatically blocks. What would be the use of such a feature?
Suppose we're talking to some old VT-100 hardware at 9600 baud. Wejust 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 enoughbuffer space in the terminal to keep a backlog of received characters. This iswhen it would be a good time to put the TTY in a blocking state. But how do wedo that from the terminal?
We have already seen that a TTY device may be configured to give certaindata bytes a special treatment. In the default configuration, for instance, areceived ^C byte won't be handed off to the application throughread(2), but will instead cause a SIGINT to be delivered tothe foreground job. In a similar way, it is possible to configure the TTY toreact on a stop flow byte and a start flow byte. These aretypically ^S (ASCII code 19) and ^Q (ASCII code 17)respectively. Old hardware terminals transmit these bytesautomatically, and expect the operating system to regulate its flow of dataaccordingly. This is called flow control, and it's the reason why yourxterm sometimes appears to lock up when you accidentally press^S.
There's an important difference here: Writing to a TTY which is stopped dueto flow control, or due to lack of kernel buffer space, will block yourprocess, whereas writing to a TTY from a background job will cause aSIGTTOU to suspend the entire process group. I don't know why thedesigners of UNIX had to go all the way to invent SIGTTOU andSIGTTIN instead of relying on blocking I/O, but my best guess is thatthe TTY driver, being in charge of job control, was designed to monitor andmanipulate whole jobs; never the individual processes within them.
To find out what the controlling TTY for your shell is called, you couldrefer to the ps l listing as described earlier, or you could simplyrun the tty(1) command.
A process may read or modify the configuration of an open TTY deviceusing ioctl(2). The API is described in tty_ioctl(4). Sinceit's part of the binary interface between Linux applications and the kernel,it will remain stable across Linux versions. However, the interface isnon-portable, and applications should rather use the POSIX wrappers describedin the termios(3) man page.
I won't go into the details of the termios(3) interface, but ifyou're writing a C program and would like to intercept ^C before itbecomes a SIGINT, disable line editing or character echoing, changethe baud rate of a serial port, turn off flow control etc. then you will findwhat you need in the aforementioned man page.
There is also a command line tool, called stty(1), to manipulateTTY devices. It uses the termios(3) API.
Let's try it!
$ stty -aThe -a flag tells stty to display all settings. By default,it will look at the TTY device attached to your shell, but you can specifyanother device with -F.
Some of these settings refer to UART parameters, some affect the linediscipline and some are for job control. All mixed up in a bucket formonsieur. Let's have a look at the first line:
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. All valid numbers are listed in /proc/tty/ldiscs. Unlisted numbers appear to be aliases for N_TTY, but don't rely on it. |
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 thexterm. The editor queries the TTY device for the current terminal sizein order to fill the entire window. Now, from a different shell window, type:
stty -F X rows Y
where X is the TTY device, andY is half the terminal height. This will update the TTY data structurein kernel memory, and send a SIGWINCH to the editor, which willpromptly redraw itself using only the upper half of the available windowarea.
The second line of stty -a output lists all the special characters.Start a new xterm and try this:
stty intr o
Now "o", rather than ^C, will send a SIGINT tothe foreground job. Try starting something, such as cat, and verifythat you can't kill it using ^C. Then, try typing "hello"into it.
Occasionally, you may come across a UNIX system where the backspace keydoesn't work. This happens when the terminal emulator transmits a backspacecode (either ASCII 8 or ASCII 127) which doesn't match the erasesetting in the TTY device. To remedy the problem, one usually types sttyerase ^H (for ASCII 8) or stty erase ^? (for ASCII 127). Butplease remember that many terminal applications use readline, whichputs the line discipline in raw mode. Those applications aren't affected.
Finally, stty -a lists a bunch of switches. As expected, they arelisted in no particular order. Some of them are UART-related, some affect theline discipline behaviour, some are for flow control and some are for jobcontrol. A dash (-) indicates that the switch is off; otherwise it ison. All of the switches are explained in the stty(1) man page, soI'll just briefly mention a few:
icanon toggles the canonical (line-based) mode. Try this in a new 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 (andconsequently outputting) one character at a time, rather than one line at atime.
echo enables character echoing, and is on by default.Re-enable canonical mode (stty icanon), and then try:
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 seewhat you type, but we're in cooked mode so the line editing facilities arestill working. Once you press enter, the line discipline will transmit the editbuffer to cat, which will reveal what your wrote.
tostop controls whether background jobs are allowed to write to the terminal. First try this:
stty tostop; (sleep 5; echo hello, world) &
The & causes the command to run as a background job. After fiveseconds, the job will attempt to write to the TTY. The TTY driver will suspendit 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 killthe background job, and try the following instead:
stty -tostop; (sleep 5; echo hello, world) &
You will get your prompt back, but after five seconds, the background jobtransmits hello, world to the terminal, in the middle of whatever youwere typing.
Finally, stty sane will restore your TTY device configuration tosomething reasonable.
I hope this article has provided you with enough information to get to termswith TTY drivers and line disciplines, and how they are related to terminals, lineediting and job control. Further details can be found in the various man pagesI've mentioned, as well as in the glibc manual (info libc, "JobControl").
Finally, while I don't have enough time to answer all the questions I get, Ido welcome feedback on this and other pages on the site. Thanks forreading!
联系客服