Chapter 10
Terminal Handling

A common type of peripheral device found on 4.4BSD systems is a hardware interface supporting one or more terminals. The most common type of interface is a terminal multiplexer, a device that connects multiple, asynchronous RS-232 serial lines, which may be used to connect terminals, modems, printers, and similar devices. Unlike the block storage devices described in Section 6.2 and the network devices to be considered in Chapter 11, terminal devices commonly process data one character at a time. Like other character devices described in Section 6.3, terminal multiplexers are supported by device drivers specific to the actual hardware.

Terminal interfaces interrupt the processor asynchronously to present input, which is independent of process requests to read user input. Data are processed when they are received, and then are stored until a process requests them, thus allowing type-ahead. Many terminal ports attach local or remote terminals on which users may log in to the system. When used in this way, terminal input represents the keystrokes of users, and terminal output is printed on the users’ screens or printers. We shall deal mostly with this type of terminal line usage in this chapter. Asynchronous serial lines also connect modems for computer-to-computer communications or serial-interface printers. When serial interfaces are used for these purposes, they generally use a subset of the system’s terminal-handling capability. Sometimes, they use special processing modules for higher efficiency. We shall discuss alternate terminal modules at the end of this chapter.

The most common type of user session in 4.4BSD uses a pseudo-terminal, or pty. The pseudo-terminal driver provides support for a device-pair, termed the master and slave devices. The slave device provides a process an interface identical to the one described for terminals in this chapter. However, whereas all other devices that provide this interface are supported by a hardware device of some sort, the slave device has, instead, another process manipulating it through the master half of the pseudo-terminal. That is, anything written on the master device is provided to the slave device as input, and anything written on the slave device is presented to the master device as input. The driver for the master device emulates all specific hardware support details described in the rest of this chapter.

10.1 Terminal-Processing Modes

4.4BSD supports several modes of terminal processing. Much of the time, terminals are in canonical mode (also commonly referred to as cooked mode or line mode), in which input characters are echoed by the operating system as they are typed by the user, but are buffered internally until a newline character is received. Only after the receipt of a newline character is the entire line made available to the shell or other process reading from the terminal. If the process attempts to read from the terminal line before a complete line is ready, the process will sleep until a newline character is received, regardless of a partial line already having been received. (The common case where a carriage return behaves like a newline character and causes the line to be made available to the waiting process is implemented by the operating system, and is configurable by the user or process.) In canonical mode, the user may correct typing errors, deleting the most recently typed character with the erase character, deleting the most recent word with the word-erase character, or deleting the entire current line with the kill character. Other special characters generate signals sent to processes associated with the terminal; these signals may abort processing or may suspend it. Additional characters start and stop output, Bush output, or prevent special interpretation of the succeeding character. The user can type several lines of input, up to an implementation-defined limit, without waiting for input to be read and then removed from the input queue. The user can specify the special processing characters or can selectively disable them.

Screen editors and programs that communicate with other computers generally run in noncanonical mode (also commonly referred to as raw mode or character-ata-time mode). In this mode, the system makes each typed character available to be read as input as soon as that character is received. All special-character input processing is disabled, no erase or other line-editing processing is done, and all characters are passed to the program reading from the terminal.

It is possible to configure the terminal in thousands of combinations between these two extremes. For example, a screen editor that wanted to receive user interrupts asynchronously might enable the special characters that generate signals, but otherwise run in noncanonical mode.

In addition to processing input characters, terminal interface drivers must do certain processing on output. Most of the time, this processing is simple: Newline characters are converted to a carriage return plus a line feed, and the interface hardware is programmed to generate appropriate parity bits on output characters. In addition to doing character processing, the terminal output routines must manage Bow control, both with the user (using stop and start characters) and with the process. Because terminal devices are slow in comparison with other computer peripherals, a program writing to the terminal may produce output much faster than that output can be sent to the terminal. When a process has filled the terminal output queue, it will be put to sleep; it will be restarted when enough output has drained.

10.2 Line Disciplines

Most of the character processing done for terminal interfaces is independent of the type of hardware device used to connect the terminals to the computer. Therefore, most of this processing is done by common routines in the tty driver or terminal handler. Each hardware interface type is supported by a specific device driver. The hardware driver is a device driver like those described in Chapter 6; it is responsible for programming the hardware multiplexer. It is responsible for receiving and transmitting characters, and for handling some of the synchronization with the process doing output. The hardware driver is called by the tty driver to do output; in turn, it calls the tty driver with input characters as they are received. Because serial lines may be used for more than just connection of terminals, a modular interface between the hardware driver and the tty driver allows either part to be replaced with alternate versions. The tty driver interfaces with the rest of the system as a line discipline. A line discipline is a processing module used to provide semantics on an asynchronous serial interface (or, as we shall see, on a software emulation of such an interface). It is described by a procedural interface, the linesw (line-switch) structure.

The linesw structure specifies the entry points of a line discipline, much as the character-device switch cdevsw lists the entry points of a character-device driver. The entry points of a line discipline are listed in Table 10.1. Like all device drivers, a terminal driver is divided into the top half, which runs synchronously when called to process a system call, and the bottom half, which runs asynchronously when device interrupts occur. The line discipline provides routines that do common terminal processing for both the top and bottom halves of a terminal driver.

Table 10.1 Entry points of a line discipline.

Image

Device drivers for serial terminal interfaces support the normal set of character-device-driver entry points specified by the character-device switch. Several of the standard driver entry points (read, write, and ioctl) immediately transfer control to the line discipline when called. (The standard tty select routine ttselect () usually is used as the device driver select entry in the character-device switch.) The open and close routines are similar; the line-discipline open entry is called when a line first enters a discipline, either at initial open of the line or when the discipline is changed. Similarly, the discipline close routine is called to exit from a discipline. All these routines are called from above, in response to a corresponding system call. The remaining line-discipline entries are called by the bottom half of the device driver to report input or status changes detected at interrupt time. The l_rint (receiver interrupt) entry is called with each character received on a line. The corresponding entry for transmit-complete interrupts is the l_start routine, which is called when output operations complete. This entry gives the line discipline a chance to start additional output operations. For the normal terminal line discipline, this routine simply calls the driver’s output routine to start the next block of output. Transitions in modem-control lines (see Section 10.7) may be detected by the hardware driver, in which case the l_modem routine is called with an indication of the new state.

The system includes several different types of line disciplines. Most lines use the terminal-oriented discipline described in Section 10.3. Other disciplines in the system support graphics tablets on serial lines and asynchronous serial network interfaces.

10.3 User Interface

The terminal line discipline used by default on most terminal lines is derived from a discipline that was present in System V, as modified by the POSIX standard, and then was modified further to provide reasonable compatibility with previous Berkeley line disciplines. The base structure used to describe terminal state in System V was the termio structure. The base structure used by POSIX and by 4.4BSD is the termios structure.

The standard programmatic interface for control of the terminal line discipline is the ioctl system call. This call changes disciplines, sets and gets values for special processing characters and modes, sets and gets hardware serial line parameters, and performs other control operations. Most ioctl operations require one argument in addition to a file descriptor and the command; the argument is the address of an integer or structure from which the system gets parameters, or into which information is placed. Because the POSIX Working Group thought that the ioctl system call was difficult and undesirable to specify—because of its use of arguments that varied in size, in type, and in whether they were being read or written—the group members chose to introduce new interfaces for each of the ioctl calls that they believed were necessary for application portability. Each of these calls is named with a tc prefix. In the 4.4 BSD system, each of these calls is translated (possibly after preprocessing) into an ioctl call.

The following set of ioctl commands apply specifically to the standard terminal line discipline, although all line disciplines must support at least the first two. Other disciplines generally support other ioctl commands. This list is not exhaustive, although it presents all the commands that are used commonly.

TIOCGETD

Get (set) the line discipline for this line.

TIOCSETD

 

TIOCGETA
TIOCSETA

Get (set) the termios parameters for this line, including line speed, behavioral parameters, and special characters (e.g., erase and kill characters).

TIOCSETAW

Set the termios parameters for this line after waiting for the output buffer to drain (but without discarding any characters from the input buffer).

TIOCSETAF

Set the termios parameters for this line after waiting for the output buffer to drain and discarding any characters from the input buffer.

TIOCFLUSH

Discard all characters from the input and output buffers.

TIOCDRAIN

Wait for the output buffer to drain.

TIOCEXCL

Get (release) exclusive use of the line.

TIOCNXCL

 

TIOCCBRK

Clear (set) the terminal hardware BREAK condition for the line.

TIOCSBRK

 

TIOCCDTR

Clear (set) data terminal ready on the line.

TIOCSDTR

 

TIOCGPGRP
TIOCSPGRP

Get (set) the process group associated with this terminal (see Section 10.5).

TIOCOUTQ

Return the number of characters in the terminal’s output buffer.

TIOCSTI

Enter characters into the terminal’s input buffer as though they were typed by the user.

TIOCNOTTY

Disassociate the current controlling terminal from the process (see Section 10.5).

TIOCSCTTY

Make the terminal the controlling terminal for the process (see Section 10.5).

TIOCSTART

Start (stop) output on the terminal.

TIOCSTOP

 

TIOCGWINSZ
TIOCSWINSZ

Get (set) the terminal or window size for the terminal line; the window size includes width and height in characters and (optionally, on graphical displays) in pixels.

10.4 The tty Structure

Each terminal hardware driver has a data structure to contain the state of each line that it supports. This structure, the tty structure (see Table 10.2), contains state information, the input and output queues, the modes and options set by the ioctl operations listed in Section 10.3, and the line-discipline number. The tty structure is shared by the hardware driver and the line discipline. The calls to the line discipline all require a tty structure as a parameter; the driver locates the correct tty according to the minor device number. This structure also contains information about the device driver needed by the line discipline.

The sections of the tty structure include:

• State information about the hardware terminal line. The t_state field includes line state (open, carrier present, or waiting for carrier) and major file options (e.g., signal-driven I/O). Transient state for Bow control and synchronization is also stored here.

Table 10.2 The tty structure.

Image

• Input and output queues. The hardware driver transmits characters placed in the output queue, t_outq. Line disciplines generally use the t_rawq and t_canq (noncanonical and canonical queues) for input; in line mode, the canonical queue contains full lines, and the noncanonical queue contains any current partial line. In addition, t_hiwat and t_lowat provide boundaries where processes attempting to write to the terminal will be put to sleep, waiting for the output queue to drain.

• Hardware and software modes and parameters, and special characters. The t_termios structure contains the information set by TIOCSETA, TIOCSETAF and TIOCSETAW. Specifically, line speed appears in the c_ispeed and c_ospeed fields of the t_termios structure, control information in the c_iflag, c_oflag, c_cflag and c_lflag fields, and special characters (end-of-file, end-of-line, alternate end-of-line, erase, word-erase, kill, reprint, interrupt, quit, suspend, start, stop, escape-next-character, status-interrupt, Bush-output and VMIN and VTIME information) in the c_cc field.

• Hardware driver information. This information includes t_oproc and t_stop, the driver procedures that start (stop) transmissions after data are placed in the output queue; t_param, the driver procedure that sets the hardware state; and t_dev, the device number of the terminal line.

• Terminal line-discipline software state. This state includes the terminal column number and counts for tab and erase processing (t_column, t_rocount and t_rocol), the process group of the terminal (t_pgrp), the session associated with the terminal (t_session), and information about any processes selecting for input or output (t_rsel and t_wsel).

• Terminal or window size (t_winsize). This information is not used by the kernel, but it is stored here to present consistent and correct information to applications. In addition, 4.4BSD supplies the SIGWINCH signal (derived from Sun Microsystems’ SunOS) that can be sent when the size of a window changes. This signal is useful for windowing packages such as X Window System [ScheiBer & Gettys, 1986] that allow users to resize windows dynamically; programs such as text editors running in such a window need to be informed that something has changed and that they should recheck the window size.

The tty structure is initialized by the hardware terminal driver’s open routine and by the line-discipline open routine.

10.5 Process Groups, Sessions, and Terminal Control

The process-control (job-control) facilities described in Section 4.8 depend on the terminal I/O system to control access to the terminal. Each job (a process group that is manipulated as a single entity) is known by a process-group ID.

Each terminal structure contains a pointer to an associated session. When a process creates a new session, that session has no associated terminal. To acquire an associated terminal, the session leader must make an ioctl system call using a file descriptor associated with the terminal and specifying the TIOCSCTTY flag. When the ioctl succeeds, the session leader is known as the controlling process. In addition, each terminal structure contains the process group ID of the foreground process group. When a session leader acquires an associated terminal, the terminal process group is set to the process group of the session leader. The terminal process group may be changed by making an ioctl system call using a file descriptor associated with the terminal and specifying the TIOCSPGRP flag. Any process group in the session is permitted to become the foreground process group for the terminal.

Signals that are generated by characters typed at the terminal are sent to all the processes in the terminal’s foreground process group. By default, some of those signals cause the process group to stop. The shell creates jobs as process groups, setting the process group ID to be the PID of the first process in the process group. Each time it places a new job in the foreground, the shell sets the terminal process group to the new process group. Thus, the terminal process group is the identifier for the process group that is currently in control of the terminal—that is, for the process group running in the foreground. Other process groups may run in the background. If a background process attempts to read from the terminal, its process group is sent another signal, which stops the process group. Optionally, background processes that attempt terminal output may be stopped as well. These rules for control of input and output operations apply to only those operations on the controlling terminal.

When carrier is lost for the terminal—for example, at modem disconnect—the session leader of the session associated with the terminal is sent a SIGHUP signal. If the session leader exits, the controlling terminal is revoked, and that invalidates any open file descriptors in the system for the terminal. This revocation ensures that processes holding file descriptors for a terminal cannot still access the terminal after the terminal is acquired by another user. The revocation operates at the vnode layer. It is possible for a process to have a read or write sleeping for some reason—for example, it was in a background process group. Since such a process would have already resolved the file descriptor through the vnode layer, a single read or write by the sleeping process could complete after the revoke system call. To avoid this security problem, the system checks a tty generation number when a process wakes up from sleeping on a terminal, and, if the number has changed, restarts the read or write system call.

10.6 C-lists

The terminal I/O system deals with data in blocks of widely varying sizes. Most input and output operations deal with single characters (typed input characters and their output echoes). Input characters are usually aggregated with previous input to form lines of varying sizes. Some output operations involve larger numbers of data, such as screen updates or other command output. The data structures originally designed for terminal drivers, the character block, C-block, and character list, C-list, are still in use in 4.4BSD. Each C-block is a fixed-size buffer that contains a linkage pointer and space for buffered characters and quoting information. Its size is a power of 2, and it is aligned such that the system can compute boundaries between blocks by masking off the low-order bits of a pointer. 4.4BSD uses 64-byte C-blocks, storing 52 characters and an array of quoting flag s (1-bit per character). A queue of input or output characters is described by a C-list, which contains pointers to the first and final characters, and a count of the number of characters in the queue (see Fig. 10.1). Both of the pointers point to characters stored in C-blocks. When a character is removed from a C-list queue, the count is decremented, and the pointer to the first character is incremented. If the pointer has advanced beyond the end of the first C-block on the queue, the pointer to the next C-block is obtained from the forward pointer at the start of the current C-block. After the forward pointer is updated, the empty C-block is placed on a free chain. A similar process adds a character to a queue. If there is no room in the current buffer, another buffer is allocated from the free list, the linkage pointer of the last buffer is set to point at the new buffer, and the tail pointer is set to the first storage location of the new buffer. The character is stored where indicated by the tail pointer, the tail pointer is incremented, and the character count is incremented. A set of utility routines manipulates C-lists:getc () removes the next character from a C-list and returns that character;putc () adds a character to the end of a C-list. The getc () routine returns an integer, and the putc () routine takes an integer as an argument. The lower 8 bits of this value are the actual character. The upper bits are used to provide quoting and other information. Groups of characters may be added to or removed from C-lists with b_to_q () and q_to_b (), respectively, in which case no additional information (e.g., quoting information) can be specified or returned. The terminal driver also requires the ability to remove a character from the end of a queue with unputc (), to examine characters in the queue with nextc(), and to concatenate queues with catq().

Figure 10.1 A C-list structure.

Image

When UNIX was developed on computers with small address spaces, the design of buffers for the use of terminal drivers was a challenge. The C-list and C-block provided an elegant solution to the problem of storing arbitrary-length queues of data for terminal input and output queues when the latter were designed for machines with small memories. On modern machines that have far larger address spaces, it would be better to use a data structure that uses less CPU time per character at a cost of reduced space efficiency. 4.4 BSD still uses the original C-list data structure because of the high labor cost of converting to a new data structure; a change to the queue structure would require changes to all the line disciplines and to all the terminal device drivers, which would be a substantial amount of work. The developers could just change the implementations of the interface routines, but the routines would still be called once per character unless the actual interface was changed, and changing the interface would require changing the drivers.

10.7 RS-232 and Modem Control

Most terminals and modems are connected via asynchronous RS-232 serial ports. This type of connection supports several lines, in addition to those that transmit and receive data. The system typically supports only a few of these lines. The most commonly used lines are those showing that the equipment on each end is ready for data transfer. The RS-232 electrical specification is asymmetrical: Each line is driven by one of the two devices connected and is sampled by the other device. Thus, one end in any normal connection must be wired as data-terminal equipment (DTE), such as a terminal, and the other as data-communications equipment (DCE), such as a modem. Note that terminal in DTE means endpoint: A terminal on which people type is a DTE, and a computer also is a DTE. The data-terminal ready (DTR) line is the output of the DTE end that serves as a ready indicator. In the other direction, the data-carrier detect (DCD) line indicates that the DCE device is ready for data transfer. Historically, VAX terminal interfaces were all wired as DTE (they may be connected directly to modems, or connected to local terminals with null modem cables). The terminology used in the 4.4BSD terminal drivers and commands reflects this orientation, even though many computers incorrectly use the opposite convention.

When terminal devices are opened, the DTR output is asserted so that the connected modem or other equipment may begin operation. If modem control is supported on a line, the open does not complete unless the O_NONBLOCK option was specified or the CLOCAL control flag is set for the line, and no data are transferred until the DCD input carrier is detected or the CLOCAL flag is set. Thus, an open on a line connected to a modem will block until a connection is made; the connection commonly occurs when a call is received from a remote modem. Data then can be transferred for as long as carrier remains on. If the modem loses the connection, the DCD line is turned off, and subsequent reads and writes fail.

Ports that are used with local terminals or other DTE equipment are connected with a null-modem cable that connects DTR on each end to DCD on the other end. Alternatively, the DTR output on the host port can be looped back to the DCD input. If the cable or device does not support modem control, the system will ignore the state of the modem control signals when the CLOCAL control flag is set for the line, Finally, some drivers may be configured to ignore modem-control inputs.

10.8 Terminal Operations

Now that we have examined the overall structure of the terminal I/O system and have described that system’s data structures, as well as the hardware that the system controls, we continue with a description of the terminal I/O system operation. We shall examine the operation of a generalized terminal hardware device driver and the usual terminal line discipline. We shall not cover the autoconfiguration routines present in each driver; they function in the same way as do those described in Section 14.4.

Open

Each time that the special file for a terminal-character device is opened, the hardware driver’s open routine is called. The open routine checks that the requested device was configured into the system and was located during autoconfiguration, then initializes the tty structure. If the device was not yet open, the default modes and line speed are set. The tty state is set to TS_WOPEN, waiting for open. Then, if the device supports modem-control lines, the open routine enables the DTR output line. If the CLOCAL control flag is not set for the terminal and the open call did not specify the O_NONBLOCK flag, the open routine blocks awaiting assertion of the DCD input line. Some drivers support device flags to override modem control; these flags are set in the system-configuration file and are stored in the driver data structures. If the bit corresponding to a terminal line number is set in a device’s flags, modem-control lines are ignored on input. When a carrier signal is detected on the line, the TS_CARR_ON bit is set in the terminal state. The driver then passes control to the initial (or current) line discipline through its open entry.

The default line discipline when a device is first opened is the termios terminal-driver discipline. If the line was not already open, the terminal-size information for the line is set to zero, indicating an unknown size. The line is then marked as open (state bit TS_OPEN).

Output Line Discipline

After a line has been opened, a write on the resulting file descriptor produces output to be transmitted on the terminal line. Writes to character devices result in calls to the device write entry, d_write, with a device number, a uio structure describing the data to be written, and a flag specifying whether the I/O is nonblocking. Terminal hardware drivers use the device number to locate the correct tty structure, then call the line discipline l_write entry with the tty structure and uio structure as parameters.

The line-discipline write routine does most of the work of output translation and Bow control. It is responsible for copying data into the kernel from the user process calling the routine and for placing the translated data onto the terminal’s output queue for the hardware driver. The terminal-driver write routine, ttwrite (), first checks that the terminal line still has carrier asserted (or that modem control is being ignored). If carrier is significant and not asserted, the process will be put to sleep awaiting carrier if the terminal has not yet been opened, or an error will be returned. If carrier is being ignored or is asserted, ttwrite () then checks whether the current process is allowed to write to the terminal at this time. The user may set a tty option to allow only the foreground process (see Section 10.5) to do output. If this option is set, and if the terminal line is the controlling terminal for the process, then the process should do output immediately only if it is in the foreground process group (i.e., if the process groups of the process and of the terminal are the same). If the process is not in the foreground process group, and a SIGTTOU signal would cause the process to be suspended, a SIGTTOU signal is sent to the process group of the process. In this case, the write will be attempted again when the user moves the process group to the foreground. If the process is in the foreground process group, or a SIGTTOU signal would not suspend the process, the write proceeds as usual.

When ttwrite () has confirmed that the write is permitted, it enters a loop that copies the data to be written into the kernel, checks for any output translation that is required, and places the data on the output queue for the terminal. It prevents the queue from becoming overfull by blocking if the queue fills before all characters have been processed. The limit on the queue size, the high watermark, is dependent on the output line speed; the difference between the low watermark and high watermark is approximately 1 second’s worth of output. When forced to wait for output to drain before proceeding, ttwrite () sets a flag in the tty structure state, TS_ASLEEP, so that the transmit-complete interrupt handler will awaken it when the queue is reduced to the low watermark. The check of the queue size and subsequent sleep must be ordered such that any interrupt is guaranteed to occur after the sleep. See Fig. 10.2 for an example, presuming a uniprocessor machine.

Figure 10.2 Pseudocode for checking the output queue in a line discipline.

Image

Once errors, permissions, and Bow control have been checked, ttwrite () copies the user’s data into a local buffer in chunks of at most 100 characters using uiomove(). (A value of 100 is used because the buffer is stored on the stack, and so cannot be large.) When the terminal driver is configured in noncanonical mode, no per-character translations are done, and the entire buffer is processed at once. In canonical mode, the terminal driver locates groups of characters requiring no translation by scanning through the output string, looking up each character in turn in a table that marks characters that might need translation (e.g., newline), or characters that need expansion (e.g., tabs). Each group of characters that requires no special processing is placed into the output queue using b_to_q (). Trailing special characters are output with ttyoutput (). In either case, ttwrite () must check that enough C-list blocks are available; if they are not, it waits for a short time (by sleeping on lbolt for up to 1 second), then retries.

The routine that does output with translation is ttyoutput (), which accepts a single character, processes that character as necessary, and places the result on the output queue. The following translations may be done, depending on the terminal mode:

• Tabs may be expanded to spaces.

• Newlines may be replaced with a carriage return plus a line feed.

As soon as data are placed on the output queue of a tty, ttstart () is called to initiate output. Unless output is already in progress or has been suspended by receipt of a stop character, ttstart () calls the hardware-driver start routine specified in the tty’s t_oproc field. Once all the data have been processed and have been placed into the output queue, ttwrite () returns an indication that the write completed successfully, and the actual serial character transmission is managed asynchronously by the device driver.

Output Top Half

The device driver handles the hardware-specific operation of character transmission, as well as synchronization and Bow control for output. The structure of the start() routine varies little from one driver to another. There are two general classes of output mechanisms, depending on the type of hardware device. The first class operates on devices that are capable of direct-memory-access (DMA) output, which can fetch the data directly from the C-list block. For this class of device, the device fetches the data from main memory, transmits each of the characters in turn, and interrupts the CPU when the transmission is complete. Because the hardware fetches data directly from main memory, there may be additional requirements on where the C-list s can be located in physical memory.

The other extreme for terminal interfaces are those that do programmed I/O, potentially on a character-by-character basis. One or more characters are loaded into the device’s output-character register for transmission. The CPU must then wait for the transmit-complete interrupt before sending more characters. Because of the many interrupts generated in this mode of operation, several variants have been developed to minimize the overhead of terminal I/O.

One approach is to compute in advance as much as possible of the information needed at interrupt time. (Generally, the information needed is a pointer to the next character to be transmitted, the number of characters to be transmitted, and the address of the hardware device register that will receive the next character.) This strategy is known as pseudo-DMA; the precomputed information is stored in a pdma structure. A small assembly-language routine receives each hardware transmit-complete interrupt, transmits the next character, and returns. When there are no characters left to transmit, it calls a C-language interrupt routine with an indication of the line that completed transmission. The normal driver thus has the illusion of DMA output, because it is not called until the entire block of characters has been transmitted.

Another approach is found on hardware that supports periodic polling interrupts instead of per-character interrupts. Usually, the period is settable based on the line speed. A final variation is found in hardware that can buffer several characters at a time in a silo and that will interrupt only when the silo has been emptied completely. In addition, some hardware devices are capable of both DMA and a variant of character-at-a-time I/O, and can be programmed by the operating system to operate in either mode.

After an output operation is started, the terminal state is marked with TS_BUSY so that new transmissions will not be attempted until the current one completes.

Output Bottom Half

When transmission of a block of characters has been completed, the hardware multiplexer interrupts the CPU; the transmit interrupt routine is then called with the unit number of the device. Usually, the device has a register that the driver can read to determine which of the device’s lines have completed transmit operations. For each line that has finished output, the interrupt routine clears the TS_BUSY flag. The characters that have been transmitted were removed from the C-list when copied to a local buffer by the device driver using getc () or q_to_b (); or if they were not, the driver removes them from the output queue using ndflush (). These steps complete one section of output.

The line-discipline start routine is called to start the next operation; as noted, this routine generally does nothing but call the driver start routine specified in the terminal t_oproc field. The start routine now checks to see whether the output queue has been reduced to the low watermark, and, if it has been, whether the top half is waiting for space in the output queue. If the TS_ASLEEP flag is set, the output process is awakened. In addition, selwakeup () is called, and, if a process is recorded in t_wsel as selecting for output, that process is notified. Then, if the output queue is not empty, the next operation is started as before.

Input Bottom Half

Unlike output, terminal input is not initiated by a system call, but rather arrives asynchronously when the terminal line receives characters from the keyboard or other input device. Thus, the input processing in the terminal system occurs mostly at interrupt time. Most hardware multiplexers interrupt each time that a character is received on any line. They usually provide a silo that stores received characters, along with the line number on which the characters were received and any associated status information, until the device handler retrieves the characters. Use of the silo prevents characters from being lost if the CPU has not processed a received-character interrupt by the time that the next character arrives. On many devices, the system can avoid per-character interrupts by programming the device to interrupt only after the silo is partially or completely full. However, the driver must then check the device periodically so that characters do not stagnate in the silo if additional input does not trigger an interrupt. If the device can also be programmed to interrupt a short time after the first character enters the silo, regardless of additional characters arriving, these periodic checks of the device by the driver can be avoided. Characters cannot be allowed to stagnate because input flow-control characters must be processed without much delay, and users will notice any significant delay in character echo as well. The drivers in 4.4BSD for devices with such timers always use the silo interrupts. Other terminal drivers use per-character interrupts until the input rate is high enough to warrant the use of the silo alarm and a periodic scan of the silo.

When a device receiver interrupt occurs, or when a timer routine detects input, the receiver-interrupt routine reads each character from the input silo, along with the latter’s line number and status information. Normal characters are passed as input to the terminal line discipline for the receiving tty through the latter’s l_rint entry:

             (*linesw[tp->t_line].l_rint)(input-character, tp);

The input character is passed to the l_rint routine as an integer. The bottom 8 bits of the integer are the actual character. Characters received with hardware-detected parity errors, break characters, or framing errors have flags set in the upper bits of the integer to indicate these conditions.

The receiver-interrupt (l_rint) routine for the normal terminal line discipline is ttyinput (). When a break condition is detected (a longer-than-normal character with only 0 bits), it is ignored, or an interrupt character or a null is passed to the process, depending on the terminal mode. The interpretation of terminal input described in Section 10.1 is done here. Input characters are echoed if desired. In noncanonical mode, characters are placed into the raw input queue without interpretation. Otherwise, most of the work done by ttyinput () is to check for characters with special meanings and to take the requested actions. Other characters are placed into the raw queue. In canonical mode, if the received character is a carriage return or another character that causes the current line to be made available to the program reading the terminal, the contents of the raw queue are added to the canonicalized queue and ttwakeup () is called to notify any process waiting for input. In noncanonical mode, ttwakeup () is called when each character is processed. It will awaken any process sleeping on the raw queue awaiting input for a read and will notify processes selecting for input. If the terminal has been set for signal-driven I/O using fcntl and the FASYNC flag, a SIGIO signal is sent to the process group controlling the terminal.

Ttyinput() must also check that the input queue does not become too large, exhausting the supply of C-list blocks; input characters are discarded when the limit (1024 characters) is reached. If the IXOFF termios flag is set, end-to-end flow control is invoked when the queue reaches half full by output of a stop character (normally XOFF or control-S).

Up to this point, all processing is asynchronous, and occurs independent of whether a read call is pending on the terminal device. In this way, type-ahead is allowed to the limit of the input queues.

Input Top Half

Eventually, a read call is made on the file descriptor for the terminal device. Like all calls to read from a character-special device, this one results in a call to the device driver’s d_read entry with a device number, a uio structure describing the data to be read, and a flag specifying whether the I/O is nonblocking. Terminal device drivers use the device number to locate the tty structure for the device, then call the line discipline l_read entry to process the system call.

The l_read entry for the terminal driver is ttread (). Like ttwrite (), ttread () first checks that the terminal line still has carrier (and that carrier is significant); if not, it goes to sleep or returns an error. It then checks to see whether the process is part of the session and the process group currently associated with the terminal. If the process is a member of the session currently associated with the terminal, if any, and is a member of the current process group, the read proceeds. Otherwise, if a SIGTTIN would suspend the process, a SIGTTIN is sent to that process group. In this case, the read will be attempted again when the user moves the process group to the foreground. Otherwise, an error is returned. Finally, ttread () checks for data in the appropriate queue (the canonical queue in canonical mode, the raw queue in noncanonical mode). If no data are present, ttread () returns the error EWOULDBLOCK if the terminal is using nonblocking I/O; otherwise, it sleeps on the address of the raw queue. When ttread () is awakened, it restarts processing from the beginning because the terminal state or process group might have changed while it was asleep.

When characters are present in the queue for which ttread () is waiting, they are removed from the queue one at a time with getc () and are copied out to the user’s buffer with ureadc (). In canonical mode, certain characters receive special processing as they are removed from the queue: The delayed-suspension character causes the current process group to be stopped with signal SIGTSTP, and the endof-file character terminates the read without being passed back to the user program. If there was no previous character, the end-of-file character results in the read returning zero characters, and that is interpreted by user programs as indicating end-of-file. However, most special processing of input characters is done when the character is entered into the queue. For example, translating carriage returns to newlines based on the ICRNL flag must be done when the character is first received because the newline character wakes up waiting processes in canonical mode. In noncanonical mode, the characters are not examined as they are processed.

Characters are processed and returned to the user until the character count in the uio structure reaches zero, the queue is exhausted, or, if in canonical mode, a line terminator is reached. When the read call returns, the returned character count will be the amount by which the requested count was decremented as characters were processed.

After the read completes, if terminal output was blocked by a stop character being sent because the queue was filling up, and the queue is now less than 20-percent full, a start character (normally XON, control-Q) is sent.

The stop Routine

Character output on terminal devices is done in blocks as large as possible, for efficiency. However, there are two events that should cause a pending output operation to be stopped. The first event is the receipt of a stop character, which should stop output as quickly as possible; sometimes, the device receiving output is a printer or other output device with a limited buffer size. The other event that stops output is the receipt of a special character that causes output to be discarded, possibly because of a signal. In either case, the terminal line discipline calls the character device driver’s d_stop entry to stop any current output operation. Two parameters are provided: a tty structure and a flag that indicates whether output is to be flushed or suspended. Theoretically, if output is Bushed, the terminal discipline removes all the data in the output queue after calling the device stop routine. More practically, the flag is ignored by most current device drivers.

The implementation of the d_stop routine is hardware dependent. Different drivers stop output by disabling the transmitter, thus suspending output, or by changing the current character count to zero. Drivers using pseudo-DMA may change the limit on the current block of characters so that the pseudo-DMA routine will call the transmit-complete interrupt routine after the current character is transmitted. Most drivers set a flag in the tty state, TS_FLUSH, when a stop is to flush data, and the aborted output operation will cause an interrupt. When the transmit-complete interrupt routine runs, it checks the TS_FLUSH flag, and avoids updating the output-queue character count (the queue has probably already been flushed by the time the interrupt occurs). If output is to be stopped but not Bushed, the TS_TTSTOP flag is set in the tty state; the driver must stop output such that it may be resumed from the current position.

The ioctl Routine

Section 10.3 described the user interface to terminal drivers and line disciplines, most of which is accessed via the ioctl system call. Most of these calls manipulate software options in the terminal line discipline; some of them also affect the operation of the asynchronous serial port hardware. In particular, the hardware line speed, word size, and parity are derived from these settings. So, ioctl calls are processed both by the current line discipline and by the hardware driver.

Figure 10.3 Handling of an error return from a line discipline.

Image

The device driver d_ioctl routine is called with a device number, an ioctl command, and a pointer to a data buffer when an ioctl is done on a character-special file, among other arguments. Like the read and write routines, most terminal-driver ioctl routines locate the tty structure for the device, then pass control to the line discipline. The line-discipline ioctl routine does discipline-specific actions, including change of line discipline. If the line-discipline routine fails, the driver will immediately return an error, as shown in Fig. 10.3. Otherwise, the driver will then call the ttioctl () routine that does most common terminal processing, including changing terminal parameters. If ttioctl () fails, the driver will immediately return an error. Otherwise, some drivers implement additional ioctl commands that do hardware specific processing—for example, manipulating modem-control outputs. These commands are not recognized by the line discipline, or by common terminal processing, and thus must be handled by the driver. The ioctl routine returns an error number if an error is detected, or returns zero if the command has been processed successfully. The errno variable is set to ENOTTY if the command is not recognized.

Modem Transitions

The way in which the system uses modem-control lines on terminal lines was introduced in Section 10.7. Most terminal multiplexers support at least the set of modem-control lines used by 4.4BSD; those that do not act instead as though carrier were always asserted. When a device is opened, the DTR output is enabled, and then the state of the carrier input is checked. If the state of the carrier input changes later, this change must be detected and processed by the driver. Some devices have a separate interrupt that reports changes in modem-control status; others report such changes along with other status information with received characters. Some devices do not interrupt when modem-control lines change, and the driver must check their status periodically. When a change is detected, the line discipline is notified by a call to its l_modem routine with the new state of the carrier input.

The normal terminal-driver modem routine, ttymodem (), maintains the state of the TS_CARR_ON flag in the tty structure and processes corresponding state changes. When carrier establishment is detected, a wakeup is issued for any process waiting for an open to complete. When carrier drops on an open line, the leader of the session associated with the terminal (if any) is sent a hangup signal, SIGHUP, and the terminal queues are flushed. The return value of ttymodem () indicates whether the driver should maintain its DTR output. If the value is zero, DTR should be turned off. Ttymodem () also implements an obscure terminal option to use the carrier line for flow-control handshaking, stopping output when carrier drops and resuming when it returns.

Closing of Terminal Devices

When the final reference to a terminal device is closed, or the revoke system call is made on the device, the device-driver close routine is called. Both the line discipline and the hardware driver may need to close down gracefully. The device-driver routine first calls the line-discipline close routine. The standard line-discipline close entry, ttylclose (), waits for any pending output to drain (if the terminal was not opened with the O_NONBLOCK flag set and the carrier is still on), then flushes the input and output queues. (Note that the close may be interrupted by a signal while waiting for output to complete.) The hardware driver may clear any pending operations, such as transmission of a break. If the state bit TS_HUPCLS has been set with the TIOCHPCL ioctl, DTR is disabled to hang up the line. Finally, the device-driver routine calls ttyclose (), which flushes all the queues, increments the generation number so that pending reads and writes can detect reuse of the terminal, and clears the terminal state.

10.9 Other Line Disciplines

We have examined the operation of the terminal I/O system using the standard terminal-oriented line-discipline routines. For completeness, we now describe two other line disciplines in the system. Note that the preceding discussion of the operation of the terminal multiplexer drivers applies when these disciplines are used, as well as when the terminal-oriented disciplines are used.

Serial Line IP Discipline

The serial line IP (SLIP) line discipline is used by networking software to encapsulate and transfer Internet Protocol (IP) datagrams over asynchronous serial lines [Romkey, 1988]. (See Chapter 13 for information about IP.) The slattach program opens a serial line, sets the line’s speed, and enters the SLIP line discipline. The SLIP line discipline’s open routine associates the terminal line with a preconfigured network interface and prepares to send and receive network packets. Once the interface’s network address is set with the ifconfig program, the network will route packets through the SLIP line to the system to which it connects. Packets are framed with a simple scheme; a framing character (0300 octal) separates packets. Framing characters that occur within packets are quoted with an escape character (0333 octal) and are transposed (to 0334 octal). Escape characters within the packet are escaped and transposed (to 0335 octal).

The output path is started every time a packet is output to the SLIP interface. Packets are enqueued on one of two queues: one for interactive traffic and one for other traffic. Interactive traffic takes precedence over other traffic. The SLIP discipline places the framing character and the data of the next packet onto the output queue of the tty, escaping the framing and the escape characters as needed, and in some cases compressing packet headers. It then starts transmission by calling ttstart(), which in turn calls the device’s start routine referenced in the tty t_oproc field. It may place multiple packets onto the output queue before returning, as long as the system is not running short of C-list blocks. However, it stops moving packets into the tty output queue when the character count has reached a fairly low limit (60 bytes), so that future interactive traffic is not blocked by noninteractive traffic already in the output queue. When transmission completes, the device driver calls the SLIP start routine, which continues to place data onto the output queue until all packets have been sent or the queue hits the limit again.

When characters are received on a line that is using the SLIP discipline, the escaped characters are translated and data characters are placed into a network buffer. When a framing character ends the packet, the packet header is uncom-pressed if necessary, the packet is presented to the network protocol, and the buffer is reinitialized.

The SLIP discipline allows moderate-speed network connections to machines without specialized high-speed network hardware. It has a simple design, but has several limitations. A newer protocol, the point-to-point protocol (or PPP), addresses some of the limitations [Simpson, 1994]. However, PPP is not included in 4.4BSD.

Graphics Tablet Discipline

The tablet line discipline connects graphic devices, such as digitizing tablets, to the system using a serial line. Once the discipline is entered, it receives graphics data from the device continuously, and allows the application program to poll for the most recent information by reading from the line. The format of the information returned is dependent on that provided by the device; several different formats are supported.

Exercises

10.1  What are the two general modes of terminal input? Which mode is most commonly in use when users converse with an interactive screen editor?

10.2  Explain why there are two character queues for dealing with terminal input. Describe the use of each.

10.3  What do we mean when we say that modem control is supported on a terminal line? How are terminal lines of this sort typically used?

10.4  What signal is sent to what process associated with a terminal if a user disconnects the modem line in the middle of a session?

10.5  How is the high watermark on a terminal’s output queue determined?

10.6  Describe two methods to reduce the overhead of a hardware device that transmits a single character at a time. List the hardware requirements of each.

*10.7 Consider a facility that allowed a tutor on one terminal to monitor and assist students working on other terminals. Everything the students typed would be transmitted both to the system as input and to the tutor’s terminal as output. Everything the tutor typed would be directed to the students’ terminals as input. Describe how this facility might be implemented with a special-purpose line discipline. Describe further useful generalizations of this facility.

*10.8 The terminal line discipline supports logical erasure of input text when characters, words, and lines are erased. Remembering that other system activities continue while a user types an input line, explain what complications must be considered in the implementation of this feature. Name three exceptional cases, and describe their effects on the implementation.

**10.9 What are the advantages of the use of line disciplines by device drivers for terminal multiplexers? What are the limitations? Propose an alternative approach to the current structure of the terminal I/O system.

**10.10 Propose another buffering scheme to replace C-lists.

References

Romkey, 1988.
J. Romkey, “A Nonstandard for Transmission of IP Datagrams Over Serial Lines: SLIP,” RFC 1055, available by anonymous FTP from ds.internic.net, June 1988.

Scheifler & Gettys, 1986.
R. W. Scheifler & J. Gettys, “The X Window System,” ACM Transactions on Graphics, vol. 5, no. 2, p. 79-109, April 1986.

Simpson, 1994.
W. Simpson, “The Point-to-Point Protocol (PPP),” RFC 1661, available by anonymous FTP from ds.internic.net, July 1994.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.142.248