Terminal and Shell Data Exchange – How It Works

ptyshellterminalterminal-emulatortty

In Linux, a Terminal is associated with a Shell. The Terminal send input to the Shell (for example: pwd), and the Shell send the output back to the Terminal (for example: /home/paul).

This diagram shows the relationship between a Terminal and a Shell (assume that the Terminal I am using is gnome-terminal, and the Shell is bash):

enter image description here

Now what I want to know is what mechanism does the Terminal and the Shell use to exchange data. This is what I think happens:

  • When gnome-terminal is executed, it will create a file representing a
    serial port in the /dev/pts directory (let's say the file name is /dev/pts/0).
  • gnome-terminal will then execute the Shell associated with it (for
    example: bash), and will pass it the pts file name (the pts file name
    can be passed through the command line arguments for example).
  • Now both gnome-terminal and bash would start reading from /dev/pts/0.
  • When gnome-terminal wants to send data to bash, it would write this data to/dev/pts/0, and bash would read this data from /dev/pts/0.
  • When bash wants to send data to gnome-terminal, it would write this data to/dev/pts/0, and gnome-terminal would read this data from /dev/pts/0.

This diagram shows what I just explained:

enter image description here

Am I correct in my understanding?


Note: of course the pts file could be a tty file if we were using virtual terminals (i.e. when we are not using a GUI), but the logic would still be the same.

Best Answer

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.