tty layer (2.4)
This section focusses on the TTY layer.
This is a messy thing because, for historical reasons,
it deals with serial lines, modems, printers, and other hardware.
The user perspective
W.R. Stevens, "Adavnced Programming in the UNIX environment",
The user perspective of the tty layer is the terminal I/O.
This is a complex topic because it is used to control a lot of different
devices (terminals, modem, serial lines, ...).
The logical view is shown in the figure on the side.
Between the user input/output, mediated by the device drivers,
and the process read/write, there is the tty layer, which buffers
the data in two queues. Both queues have a maximum size.
When the output queue is full the process is put to wait.
When the input queue is full characters are lost.
If echo is enabled the input characters are echoed on the output queue.
The kernel characteristics of a tty are contained in a
(defined in include/asm/termios.h). This contains
c_iflag, the input mode flags;
c_oflag, the output mode flags;
c_lflag, the local mode flags;
c_cflag, the control mode flags;
c_line, line discipline type (this is not seen by the
c_cc, an array of control characters
A similar structure exists (
) which differ only in
the typing of the fields (POSIX like).
Userland has the
/usr/include/bits/termios.h) which contains also i/o speed fields.
The values of the flags bits and the special characters
are listed in the tables below.
||generate SIGINT on BREAK
||map CR to NL on input
||ignore characters with parity errors
||ring bell on input when the input queue is full
||map NL to CR
||enable input parity checking
||strip eighth bit off input characters
||map uppercase to lowercase on input
||exable any character to restart output
||enable start/stop input flow control
||enable start/stop output flow control
||mark parity errors
||backspace delay mask
||CR delay mask
||formfeed delay mask
||NL delay mask
||map CR to NL on output
||fill is DEL (else is NUL)
||use fill characters for delay
||map lowercase to uppercase on output
||map NL to CR-NL (this is CRMOD)
||NL performs CR function
||no CR output at column 0
||discard EOF (^D) on output
||perform output processing
||expand tabs to spaces
||horizontal tab delay mask
||vertical tab delay mask
||CTS flow control of output
||ignore control flags
||ignore modem status line
||RTS flow control on input
||character size mask
||send two stop bits (else send one)
||hangup on last close
||flow control output via Carrier
||parity is odd (else is even)
||use alternate WERASE algorithm
||echo control characters as ^Char
||visually erase characters
||echo KILL (by erasing the entire line)
||visual erase for KILL
||visula erase mode for hardcopy
||output being flushed
||enable extended input character processing
||enable terminal-generated signals
||disable flush after interrupt or quit
||no kernel output from STATUS
||retype pending input
||send SIGTTOU for background output
||canonical upper/lower presentation
||backspace one character
||reprint all input
||backspace one word
||IXON / IXOFF
||delayed suspend (SIGTSTP)
||interrupt signal (SIGINT)
||quit signal (SIGQUIT)
||suspend signal (SIGTSTP)
The user control of the tty can be done with the
function, but its last argument depends on the action being performed.
This makes type checking impossible.
Therefore POSIX defines 12 functions,
set/get the tty attributes;
cfsetospeed: for the i/o
speeds (baud rate);
tcsendbreak are line control functions;
foreground process group ID.
Canonical mode (cooked) is simple, from the user point of view:
reads are linewise, ie, the read() call returns when a complete line
has been entered by the device driver.
A line is complete when one of the following delimiters is entered:
NL, EOL, EOL2, EOF (and CR if ICRNL is set and IGNCR is not set).
Note that EOF is not returned with the read.
This is set by turning off ICANON.
The input characters are not assembled into lines.
These special characters are not processed: ERASE, KILL, EOF, NL, EOL, EOL2,
CR, REPRINT, STATUS, WERASE.
The two entries
determine the behaviour of the
There are four cases:
||read() returns immediately with whatever data are available
(from 0 if none, to the maximum number requested in the call)
||read() returns between 1 and the requested number if there are
data before the time expires; it returns 0 if the time expires
||read() returns between 1 and the requested number when there are
data available (may wait forever)
||read() returns between MIN and the number requested before the
time expires, or or between 1 and MIN-1 if the time expires.
It can block forever if there are no data
There are two noncanonical modes (i am aware of), cbreak and raw.
|| ECHO and ICANON off
|| MIN=1, TIME=0
|| ECHO, ICANON, IEXTEN and ISIG off
|| BRKINT, ICRNL, INPCK, ISTRIP, IXON off
|| CSIZE, PARENB and OPOST off
|| CS8 (set 8-bit characters)
|| MIN=1, TIME=0
To conclude this summary of terminal I/O
look at this example from Steven's book.
, set a tty in raw mode
tty kernel structures
G. Kroah-Hartman, "The tty layer", Linux Journal, Aug. 2002, 40-47
G. Kroah-Hartman, "The tty layer - Part II", Linux Journal, Oct. 2002,
G. Kroah-Hartman, "The USB serial driver layer",
Linux Journal, Feb. 2003, 38-41
G. Kroah-Hartman, "The USB serial driver layer - Part II"
Linux Journal, Apr. 2003, 32-35
The tty layer is split into two structures: the tty driver and the line
discipline. The driver handles the backend hardware; the line discipline
is like a filter on the input/output.
These two structures are contained in the
, which has
also a structure, called flip buffer, for buffering input characters, and
pointers to the termios.
These structures are defined in four header files, in the
tty_flip.h, defines two inline routines
tty_insert_flip_char: insert a char and a flag into the
tty_schedule_flip: append the flip
to the timer task queue. The routine
flush_to_ldisc(), if the low-latency flag
is set, or submit the tty's flip buffer
on the timer queue. This tqueue is initialized with the
flush_to_ldisc() routine and the tty data: see
the description of
screen_info contains video informations, and data
for the linear frame buffer of VESA graphic mode.
tty_flip_buffer contains two input buffers of size twice
TTY_FLIPBUF_SIZE (512), one of characters the other of flags
(for parity errors).
The flip buffers are logically made of two parts, and the code fills
alternatively one or the other.
buf_num denotes the part in use, and
the number of bytes that have been received.
There is a task_queue
and a semaphore
- It contains a tty_driver
- a line discipline
- pointers to the
- several flags and counters;
link to another tty_struct's (for ptty).
It is used in the
start methods of the
tty_driver: the link's read_wait queue is waken up if its
packet flag is set;
- a tty_flip_buffer
- two wait_queue_head_t,
- a tq_struct
- a list of
- other fields for the N_TTY line discipline.
Device driver, tty_driver
- It contains accessory informations:
num (number of devices),
subtype (type and subtype of the tty driver),
flags, and others
- the pointer to the
table of tty_struct;
- two pointers for a doubly-linked list,
- the tty_driver methods:
open, called after a user open.2;
close, called after a user close.2
write, called after a user write.2 or when the kernel
needs to write to the tty device
put_char, called by the kernel to write a single
flush_chars, called by the kernel after writing a number
of characters to the tty device using put_char.
write_room, returns the number of characters the tty
device will accept for writing.
ioctl, driver specific ioctl's
set_termios, used to notify the tty driver of a change
set_ldisc, used to notify the tty driver of a change
of line discipline;
throttle, tells the tty driver that the input buffers
of the line discipline are close to full;
unthrottle, tells the tty driver that the input buffers
of the line discipline have enough space;
stop, stops the tty driver from sending out characters
to the tty device;
start, starts the tty driver to send out characters
to the device;
hangup, tells the tty driver to hang up the tty device;
break_ctl, toggle the BREAK status on the RS-232 port;
wait_until_sent, the tty driver waits until the device
has written out all the characters in its FIFO queue;
send_xchar, send a high-priority XON-XOFF character
to the device.
Line discipline, tty_ldisc
- A line discipline consists of a
flags (of the line discipline),
num (index of the line discipline in the global
- It has methods called from above, ie, from the user perspective,
open, called when the line discipline is associated
with the tty. The line discipline can do any suitable initialization
and return negative on failure.
close, called when the line discipline is shut down, either
because the tty is closed or it changes the line discipline.
read, called after a user read.2.
write, called after a user write.2. It must deliver the
characters to the low-level tty device.
ioctl, called after a user ioctl.2 that is not handled
by the tty layer or the low-level tty driver.
poll, called after a user select/poll.
set_termios, called after a user set_termios.
flush_buffer, called to tell the line discipline to
send any input character in the buffer to the user process.
chars_in_buffer, returns the number of input characters
buffered by the line discipline.
- It defines methods called from below, ie from the low-level tty_driver
receive_buf, called to send
characters to the line discipline.
returns the number of characters that the line discipline can accept.
write_wakeup, called to tell the line discipline that
it can send down more characters.
inserts the tty_driver into the list
, and if it does not provide its own
It registers the tty_driver with the devfs (devfs_register_chrdev), and
with the proc filesystem (proc_tty_register_driver).
The file operations are
tty_read: after some checks it locks the kernel and calls
the line discipline read function. Then it unlocks the kernel;
tty_write: it does some checks and then calls
do_tty_write() which down's the semaphore
atomic_write, locks the kernel, calls the write function
(of the line discipline), and unlocks and ups;
tty_poll calls the poll routine of the line discipline;
tty_open: essentially it calls
to retrieve the tty_struct for the device number, and the
open routine of the tty's driver;
init_dev() allocates a new tty_struct,
and initializes it (by calling
sets up the tty's termios, termios_locked, device, driver, count;
sets up the driver's table entry for this device;
finally it calls the tty's line discipline
performs the initialization of a tty_struct.
It sets the magic number (TTY_MAGIC), the line discipline (with the
default ldiscs[N_TTY]), the fields in the tty flip_buffer (in particular
tqueue with routine
flush_to_ldisc and data
tty), the write and read wait_queues, the task_queue
Finally it initializes the semaphores
atomic_write, the list_head
the secure-attention-key task_queue
if the tty has the TTY_DONT_FLIP flag set, the flip
is queued on the timer queue.
Otherwise the buffer pairs are "flipped", the flip count
is reset to zero, and the line discipline
receive_buf is called.
The line discipline tty_ldisc_N_TTY
The line discipline
is defined in
n_tty.c with the following methods,
n_tty_open allocates a
read_buf for the
n_tty_close flushes the buffer and frees the
n_tty_flush_buffer clears everything and unthrottle the
n_tty_chars_in_buffer returns the number of characters in the
buffer to be delivered to the user, ie, the difference between
read_chan is rather complicated, but essentially it amounts to
add the current process to the tty_struct
copy characters from the buffer to the user buf.
When done remove the current process from the wait_queue.
adds the current process to the tty_struct
calls the tty driver write(tty, 1, buf, count) routine;
When done removes the current process from the wait_queue.
n_tty_receive_buf copies the received chars in the
read_buf with possible line discipline processing.
If the tty_struct is not ICANON and the
read_cnt is more
minimum_to_wake calls the fasync method on the
tty_struct. Finally it checks whether to throttle or not the device.
n_tty_write_wakeup calls the fasync method on the tty_struct
The point of this exercise is to write a memory-based tty_driver.
The hardware output should be provided by a memory buffer.
Therefore the driver write() function that sends the user data on the
output port should copy the data on this buffer.
A timer can be used to emulate a delay in the transmission of the
data. In a real situation there would be an interrupt raised by the
hardware when data arrive, and the timer tasklet would push the data
to the line disipline, so that they can be delivered to the user
[more description of the example]
You can now experiment changing the device settings and
observing how changes the call to the read/write functions.
For example, by turning ECHO on, the line discipline will
echo all the characters from the flip buffer to the write()
Marco Corvi - 2003