Hacker News new | past | comments | ask | show | jobs | submit login
The TTY demystified (linusakesson.net)
218 points by luu on July 27, 2014 | hide | past | web | favorite | 21 comments

Even after reading this, TTYs will surprise you. I've done my share of TTY programming in Linux and here be dragons.

From modes to tty types to control chars and signals, the TTY system is itself a bloated relic whose stink becomes apparent when you try to connect it to the rest of the unix philosophy, where everything is supposed to be a stateless text stream.

This stuff is really interesting for historical reasons and made sense 20 years ago, but I long for the day where the TTY system is gone and character drivers work on some standard JSON stream or something.

meh. TTY programming was hard in the 90's, and then it was done. If you find it hard now, you're probably still just learning all about it. Its an old, well and truly solved problem. The slayers of yesterdays TTY dragons are todays WebSocket hackers, I surmise.

Still, I think the TTY - and UARTS in general - have a lot of utility in this day and age, warty dragons and all. I've got a terminal server in my lab just for the case of wiring up the small, light, cheap, efficient embedded systems which still depend on the dragons to deliver the fire. Nothing quite like having a functional 9600-baud term you can tap into with an Oscilloscope and work out what the bits mean .. try doing that with a JSON-based interface. ;)

I disagree, it is still hard: https://bugs.freedesktop.org/show_bug.cgi?id=70290

Further, there are still things for which there is not a right way to do it. If you look at the man page for "unbuffer" (part of expect), there's a "CAVEATS" section that describes a bug... that can't be fixed while PTYs are in their current state. I wouldn't call it "well and truly solved".

I think you misspelled "40 years ago".

By 1994, GUIs were common on workstations, and hardware-based serial terminals were disappearing fast. OTOH, the command line as a UI paradigm is alive and well today and might well be around for another 20 or 40 years.

While getting deep into the TTY system might be hard in most cases the defaults of programs nowadays work quite well. And there is nothing easier and simper than opening a Minicom or Screen to interact with your embedded device. Besides being a much more low level interface it also shows you the shutdown and boot process where your ssh connection is not available. It's a really handy tool, although it might be annoying from time to time.

Now I feel like I want to read a similar article describing how the Plan 9 terminal interface works, just to cleanse my palate.

I learned something new from this: I was always under the impression that you could not do anything when your process is stopped via ^Z. But apparently there are two separate signals involved here: SIGTSTP (sent when ^Z is pressed and blockable) and SIGSTOP (also suspending the process but NOT blockable) (This reminds of the time SIGCLD / SIGCHLD were different).

I have a system where shell users run commands that can grab some locks, and they like to press ^Z to suspend them which isn't compatible with a simple fcntl-based lock.

vim and emacs both trap Ctrl-Z in the terminal and implement custom behavior. If they didn't, I think, you would not be dropped gracefully into a shell prompt after pressing Ctrl-Z; instead, you'd be left with the terminal in non-canonical mode with the vim or emacs process suspended, and there would be nothing graceful about that.

In your case, there should be a way to trap attempted Ctrl-Z suspends in your process and break the locks (or do whatever you need to do).

I have read a great book covering TTY program on ux machines, it is also great for getting an understanding on how it all works. The book title is : "Advanced Programming in the Unix Environment" By W. Richard Stevens and Stephen A. Rago

It gives a thorough walk through of the ttys and pty's, IMHO it is a must read for anybody wanting do to TTY programming, and it is also a great source for understanding how the streams works in connection with the tty's/pty's.

> Line editing...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

Actually this is backwards.

Line discipline is a historical artifact. Typically mainframes had IO front ends (AKA channel controllers) than managed IO so that the CPU (which BTW was what was charged for) could be kept busy and not wait around for users (by contrast CTSS, ITS and later machines like the Alto expressed an alternative, radical idea which was that the computer was there to support the user). Basically once a line was ready it was submitted by pressing the enter key and then its process was unblocked and processed the data. (Nowadays we just queue an event on the main thread...essentially the same thing!).

Likewise, a number of early network protocols like X.25 would charge per "line" (more about that later).

Unix was originally written for the PDP-7 (& later 11) which were minicomputers that didn't have channel controllers (in fact the earliest arpanet interfaces that weren't IMPS were PDP-11s acting as networking channel controllers for PDP-10 mainframes). Since IO was done by the kernel, you didn't want to to an expensive context switch for the common case of just reading a character and putting it into an input buffer.

So despite the comment quoted above, the true Unix model would have been for the kernel to have been agnostic and for each program to decide how it wanted to handle character input. But that would have been too expensive, so a more pragmatic approach prevailed.

By the way there are all sorts of other fossils in the TTY system. Multics used # to delete a character and @ to rub out the whole line -- a legacy of printing characters that persisted into Unix for years ###### decades. This seems weird today, but originally Multics (and later Unix) didn't have traditional line disciplines, or even control characters, much less select(). So it was handy to be able to essentially do immediate mode editing right in the input buffer.

Another fossil is that vi is just a visual mode grafted onto ed (clone of Multics qed IIRC), which was an editor with commands based on line-at-a-time IO.

ITS got proper IO virtualization first (and the predecessor to select, and extended it to the network with SUPDUP, later also implemented by Multics and eventually Unix, which made using Emacs possible) and actually had a more general version of select() before Unix was written, but at the time it was considered exotic.

Oh and the X.25 comment? Well in 1982 I was working in France and the only way for me and my colleagues who had come from MIT to get back to our mail at the MIT AI lab was a connect across the ocean on an international X.25 network hooked into MIT Multics and thence connect to the AI lab. After using fully interactive systems, it was painful to use a line-at-a-time system, so I figured out how to reset the X.25 front end to ship a whole packet for every character typed (packets had a maximum length so if you typed a really long line it would send a packet's worth, so I hacked it into thinking that a packet length was 1). Which was great, until shortly after the end of the month when someone "upstairs" had a heart attack at the size of the networking bill...

I recently wanted to give a command-line program the ability to wait for and respond to keypresses instantly, without the user having to type Enter. It sounded simple enough. Here's what you have to do to set this up:

  // headers for terminal control
  #include <termios.h>
  #include <unistd.h>

  // global variable
  struct termios saved_attributes;

  // prototype of atexit callback
  void reset_input_mode (void);

  // --- inside main()

  // local variable
  struct termios termios_data;

  // tell stdio not to buffer output
  setvbuf(stdout, NULL, _IONBF, 0);

  // incantation to the terminal
  tcgetattr(0, &termios_data);
  saved_attributes = termios_data;
  termios_data.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN);
  termios_data.c_cc[VMIN] = 1;
  tcsetattr(0, TCSANOW, &termios_data);
  // set atexit callback so we can restore the prior terminal mode upon exit

  // --- outside main()

  // implementation of atexit callback -- restores terminal to prior mode
  void reset_input_mode (void)
    tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes);

Now, you can read input one byte at a time (with, e.g. getchar(3)) and output appears instantly.

Uh! It makes me remember some obscure and mystic stuff for the IBM 3151 terminal [1]. This terminal has an auxiliary port where you can connect the printer, so it was a (not so) dumb terminal where you can connect other peripherals. There was a government project to attach an individual printer (instead of connecting it to the server) to some terminals but nobody knew the escape codes to control that port. At some point the IBM guys sent a difficult to find IBM 3151 with these details. Probably this manual is now already available on Internet.

[1] http://ps-2.kev009.com/tl/techlib/manuals/adoclib/aixasync/a...

Terminal.app and other terminal emulators are the prototypical case where a TTY -- more precisely, a TTY/PTY pair -- are required these days.

Every virtual terminal in the Linux text-mode console also requires a TTY/PTY pair.

Shell mode (and more generally any mode derived from Comint mode) in Emacs also requires a TTY/PTY pair: although the Emacs documentation claims that Comint-derived modes can be configured to use a pipe instead, a comment in comint.el confesses that no one has gotten that to actually work.

And of course, SSHing into a remote host requires at least one TTY/PTY pair (on the remote host).

Are there other place in the software on, e.g., a Mac reliant on a TTY? If there are, please tell me what they are!

Both `ssh` and `sudo` will only ask you for a password from a TTY; they'll refuse to work if they can't find a TTY (unless, of course you have a graphical prompt configured).

The `expect` suite of tools uses a TTY/PTY pair to script programs that might otherwise be unhappy with not having a PTY. Same with the `script` program, which on my system is part of the 'util-linux' package.

On GNU/Linux, `systemd-nspawn` (a chroot-like tool) uses a TTY/PTY pair to isolate the child process from the outside world.

This guy's other articles are gold too.

It may be worth noting that both of the kernel source files mentioned have moved from `drivers/char/` to `drivers/tty/`. I think that all of the other details are up to date, though.

This was a great read, I hope it stays on the front page until morning so more people see it. It filled some gaps left from my Operating Systems class about TTY, especially the history with Teletypes.

I wish he had said something about'^?' vs '^H'. To this day I still struggle sometimes when logged into a Unix machine and run into this problem.

ASCII DEL (^?) is natively a ‘forward delete’ operation; it erases the character under the cursor while advancing to the next character. That's why its value is 0x7F: on paper tape it's all holes, so it can punch over any other character.

The problem arose from two later, non-Unix (and arguably deliberately anti-Unix¹) influences.

One, as the Debian notes linked in a sibling comment mentions, was GNU Emacs, whose bindings came from custom non-ASCII, bucky-bit² devices, and gleefully stepped on ASCII control characters like Backspace (^H) and Device Control 3 (^S).

The other was DEC's new VT2x0 series of terminals, with the VMS-oriented LK201 keyboard³, that hid Backspace and Escape on unlabelled function keys (F12 and F11 respectively) and made the key in the typist's backspace position send DEL. (The VT100⁴ had had both Backspace, in the typist's location, and DEL, where US PC keyboards have \|.) This was the big one, as BSD 4.2 for the VAX spread and dragged the LK201 along with it.

¹ http://richard.esplins.org/static/downloads/unix-haters-hand...

² http://en.wikipedia.org/wiki/Bucky_bit

³ http://deskthority.net/wiki/DEC_LK201


The best policy to achieve a consistent behavior from all programs for backspace, delete, Ctrl-H, etc. is the one used by the Debian project:


This was one of the things that convinced me to start using Debian more than 15 years ago. They, like no other Unix at the time, had looked really deeply into the problem and come up with a good configuration to best fit all programs and systems. I, as a system administrator who was familiar with this problem, was very impressed by their solution, and I deemed it better than the one I had (which I have since forgotten).

All these years later and under Linux I still can't unambiguously read a UART line break condition....

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact