Originally, "tty" had two definitions: the hardware (now the emulator) and the driver (interfaced through /dev/pty* or /dev/tty*).
The hardware/emulator was/is responsible for:
- Taking a stream of data and presenting it; this included interpreting control sequences like "move cursor left", "blinking cursor", "clear-screen" although these control sequences were often different among manufacturers.
- Sending keycodes for keys typed by the user; most of these were standard ASCII characters, but some terminals sent propriety keycodes for even standard keys.
The 'tty' driver was responsible for:
- Managing the buffering, in raw or canonical mode; for example, buffering a line of characters until Enter is pressed.
- Managing the control flow; e.g. being able to stop/continue with Cntl-s/Cntl-q.
- Translating propriety keycodes to standard ASCII, where applicable.
- Intercepting certain control characters (like Cntl-c and Backspace) and processing them appropriately (send SIGINT on a Cntl-c or signal an EOF on Cntl-d.
- Canonical display of characters, for example, if
echo
is turned off, then do not send feedback (character typed) back to the terminal.
The terminfo and termcap databases managed what terminal control characters should be sent for an operation (like 'clear-screen'). These control sequences were/are not interpreted by the driver, but by the hardware/emulator.
You're missing a necessary piece. Pseudo-tty devices are not symmetrical like sockets. There is a master end and a slave end. The files in /dev/pts
represent the slave devices.
The terminal emulator creates a pseudo-tty by calling openpty
(or forkpty
which is openpty
plus some bonus setup for the common case where you want to run a new process on your new tty). At a lower level this involves opening /dev/ptmx
and doing some magic ioctls.
As a result of calling openpty
the terminal emulator gets a pair of file descriptors, and can also get the name of the file in /dev/pts
corresponding to the slave. The master doesn't get an individual name because there's never any need for a child process to open it by name.
Master and slave devices do act sort of like opposite ends of a socket: what you write on one end is read from the other. But because this is a tty, all the tty modes are applied to the data on the way through.
For example, if you are a terminal emulator, and you receive a keypress of the A key, you should write 'a'
to the master file descriptor. This is directly equivalent to sending that byte over the serial line from the terminal to the computer. It will result in 'a'
being read from the slave (by whatever program is reading it - e.g. the shell).
If you receive a keypress of the D key while the Ctrl key is down, you should write a 4
byte ('D' ^ 0x40
) to the master file descriptor. (Because that's what a real terminal sends on the wire.) What happens next depends on the tty mode. In raw mode, the program reading the slave tty will see a 4
byte. In cooked mode, the tty will activate the "EOF special key pressed" behavior.
In the reverse direction, there is also some processing. When some program writes '\n'
to the slave tty, you will probably receive "\r\n"
on the master file descriptor, because of onlcr
post-processing.
History section, skip if bored
Long ago, the slave devices had names like /dev/ttyp0
and each one had a corresponding master, like /dev/ptyp0
. They weren't dynamically created. A terminal emulator could just probe them all to find one not currently in use, and start using it. Managing ownership and permissions was a problem. xterm
was setuid-root just so it could chown the slave.
The new scheme, called "UNIX98 ptys", handles device creation and ownership through the magic ioctls, so files only appear in /dev/pts
when they're in use, and they are owned by the user who ran the program that created them.
Best Answer
Your description doesn't quite match your diagram, and is more correct than your diagram.
The X server does not use the tty driver for either input or output. It reads inputs directly from the drivers for the various input devices and sends output directly to the graphics card drivers.
You can list the input devices with
xinput
and then get further information withxinput list-props
. For example:You can see that my X server obtains input from my USB keyboard by reading from
/dev/input/event2
.For output, I don't know if there's a similar user-level tool.
xrandr --listproviders
lists the graphics drivers that are in use or available, but does not list/dev
entries. You can see which graphics device the X server has open withlsof -p$(pgrep Xorg)
orless /var/log/Xorg.0.log
.The concept of controlling terminal was designed for text mode sessions. An X server may or may not have a controlling terminal depending on how it was launched. An X program that was started from a GUI menu typically doesn't have a controlling terminal, because the window manager doesn't have one. An X program started from a shell running in a terminal does have that terminal as a controlling terminal.