Kernel, PTY and TTY

PTY (Pseudo-TTY), TTY (TeleTYpewriter).

TTY

The term TTY originates from the early days of computing when teletype machines were used as the main way for hci. As technology evolved, the physical teletype machines were replaced with virtual terminals or screens, and TTY is used as interface for text-based communication between a user and the system. TTYs can be hardware-based, like a console, or software-based, commonly known as virtual consoles or terminals (e.g., /dev/tty1, /dev/tty2, etc.).

PTY

A PTY is a Pseudo Terminal, which is a software emulation of a TTY. It allows for the creation of a terminal interface without the need for any actual terminal hardware. A PTY is typically used for providing terminal functionality to processes whose input and output are not directly connected to a physical terminal. PTY is used when you open a terminal window in GUI or SSH. It is used for bridge for real-TTY emulation and dealing with user-facing end of TTY.

TTY can be thought as two parts. IO, and TTY driver inside of kernel. When process is created, it is attched to some kind of IO device like TTY device as stdout/stdin/stderr. Just like writing to files via file descriptors, outputing and inputing act of process is a wrapper for read() and write() to “0, 1, 2” file descriptors. What printf actually does is fprintf(stdout, ...), in libc.

Just like in TTY, PTY acts simmilarly, just that it doesnt have actual “hardware” and matching driver, it just differently handles it.

PTY also has two ends: the master end and the slave end. The master end is used by a terminal emulator, like xterm or GNOME Terminal, to control the terminal session. The slave end provides the interface that emulates the physical terminal, where programs like shells (bash, zsh, …) read input from and write output to, as if they were interacting with a real TTY.

PTY and TTY

When we open new terminal emulator with shell and check shell’s /proc/{pid}/fd/, we can see 0, 1, 2 is a symlink to some /dev/pts/{id}. If we put data (char data) to /dev/tty0 as write(), in kernel (because write is also syscall), data is forwarded to driver code, and then driver transforms and sends char data to TTY as some kind of electronic signal. In case of PTY, it is just same but now instead of driver, kernel just forwards data to other end, /dev/pty#, and master process (like terminal emulator) handles that data just like kernel driver, but now it just makes display image out of it (GUI), or sends it to web socket (SSH).

static int pty_write(struct tty_struct * tty, int from_user,
		       const unsigned char *buf, int count)
{
	struct tty_struct *to = tty->link;
	int	c=0, n;
	char	*temp_buffer;

	if (!to || tty->stopped)
		return 0;

	if (from_user) {
		down(&tty->flip.pty_sem);
		temp_buffer = &tty->flip.char_buf[0];
		while (count > 0) {
			/* check space so we don't copy needlessly */ 
			n = MIN(count, to->ldisc.receive_room(to));
			if (!n) break;

			n  = MIN(n, PTY_BUF_SIZE);
			n -= copy_from_user(temp_buffer, buf, n);
			if (!n) {
				if (!c)
					c = -EFAULT;
				break;
			}

			/* check again in case the buffer filled up */
			n = MIN(n, to->ldisc.receive_room(to));
			if (!n) break;
			buf   += n; 
			c     += n;
			count -= n;
			to->ldisc.receive_buf(to, temp_buffer, 0, n);
		}
		up(&tty->flip.pty_sem);
	} else {
		c = MIN(count, to->ldisc.receive_room(to));
		to->ldisc.receive_buf(to, buf, 0, c);
	}
	
	return c;
}
static int pty_open(struct tty_struct *tty, struct file * filp)
{
	int	retval;
	int	line;
	struct	pty_struct *pty;

	retval = -ENODEV;
	if (!tty || !tty->link)
		goto out;
	line = MINOR(tty->device) - tty->driver.minor_start;
	if ((line < 0) || (line >= NR_PTYS))
		goto out;
	pty = (struct pty_struct *)(tty->driver.driver_state) + line;
	tty->driver_data = pty;

	retval = -EIO;
	if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
		goto out;
	if (test_bit(TTY_PTY_LOCK, &tty->link->flags))
		goto out;
	if (tty->link->count != 1)
		goto out;

	clear_bit(TTY_OTHER_CLOSED, &tty->link->flags);
	wake_up_interruptible(&pty->open_wait);
	set_bit(TTY_THROTTLED, &tty->flags);
	retval = 0;
out:
	return retval;
}
int __init pty_init(void)
{
	int i;
    memset(&dev_tty_driver, 0, sizeof(struct tty_driver));
	dev_tty_driver.magic = TTY_DRIVER_MAGIC;
	dev_tty_driver.driver_name = "/dev/tty";
	dev_tty_driver.name = dev_tty_driver.driver_name + 5;
	dev_tty_driver.name_base = 0;
	dev_tty_driver.major = TTYAUX_MAJOR;
	dev_tty_driver.minor_start = 0;
	dev_tty_driver.num = 1;
	dev_tty_driver.type = TTY_DRIVER_TYPE_SYSTEM;
	dev_tty_driver.subtype = SYSTEM_TYPE_TTY;
	
	if (tty_register_driver(&dev_tty_driver))
		panic("Couldn't register /dev/tty driver\n");

	dev_syscons_driver = dev_tty_driver;
	dev_syscons_driver.driver_name = "/dev/console";
	dev_syscons_driver.name = dev_syscons_driver.driver_name + 5;
	dev_syscons_driver.major = TTYAUX_MAJOR;
	dev_syscons_driver.minor_start = 1;
	dev_syscons_driver.type = TTY_DRIVER_TYPE_SYSTEM;
	dev_syscons_driver.subtype = SYSTEM_TYPE_SYSCONS;

	if (tty_register_driver(&dev_syscons_driver))
		panic("Couldn't register /dev/console driver\n");
    ...
}