As far as possible all input and output in Linux is made to look like input and output to an ordinary file. However, terminal I/O is one of the more difficult cases. As long as you just want to treat your terminal like a sequential access file for input and output, then you can use the open(), close(), read() and write() system calls you have already seen. Even so, there are some differences.
Normally, a process will automatically be attached to a terminal, via file descriptors 0,1 and 2, which it will inherit from its parent process. If this is the case, then the open() calls have already been done on behalf of the process and the attached terminal will be the control terminal for the process (we'll see the implications of control terminals later).
If you need to open a terminal for yourself then you will need a file name to pass as a parameter to open(). There are basically two possibilities: either you can use the special file name /dev/tty which refers to your control terminal, if you have one, or you need know the name of a specific terminal device special file, such as /dev/ttyl, /dev/tty2 etc. for the file names of the virtual console terminals.
A moment's thought will convince you that the O_CREAT O_EXCL O_TRUNC and O_APPEND flags don't make any sense when applied to terminal files. There are however, two new flags that do:
O_NOCTTY | stop this terminal being the control terminal |
O_NONBLOCK | stop open(), read() and write() from blocking. |
For superficial compatibility with some other UNIX systems an O_NDELAY flag is also provided. Under Linux O_NDELAY is exactly the same as O_N0NBL0CK, though its semantics should really he slightly different.
Once again, we'll look at control terminals a little later, but what is this idea about blocking? Suppose you do a read() on a terminal keyboard and the user hasn't pressed any keys, what should happen?
In fact what does happen is that when you are reading from a terminal and no keys have been pressed the read() call doesn't return. Effectively, the process is blocked or suspended until the read() request can be satisfied in some way.
Inside the Linux kernel there are data buffers associated with each terminal file. These are used by default to buffer characters as they are sent to and received from the terminal device files. The default action of these buffers is to store up characters until a newline (\n) is encountered. In the case of the keyboard buffer, only when the newline has been received will a blocked read() call be released and the characters be made available to the reading process. It doesn't matter how many bytes were requested, the read() call cannot return more characters than are in the buffer up to the first newline character. If less than this number of characters is requested then read 0 will just return the number of characters asked for. Either way, the return value from read() will be the number of characters actually read.
If the O_NONBLOCK flag is set on open() then the blocking behavior of read() is suppressed. Now if there is no data ready when the terminal is read then the read() call returns the value -1 and errno is set to the value EAGAIN, to indicate that the read() attempt should be retried. For compatibility with other UNIX flavors the errno error value EWOULDBLOCK is also defined and has exactly the same value as EAGAIN.
In the case of a write() call to a terminal device, blocking is not an issue and the characters written are just buffered until a newline is sent, when the buffer contents will be displayed.
The close() call works on a terminal device just like an ordinary file and releases the file descriptor for further use.
As long as all you want to do with a terminal device is to use open(), close(), read() and write() then the feel of terminal access is very similar to ordinary file access. However, there are many things you may wish to do to set up a terminal interface that do not fit into this model. For example, it is not clear how you would use read() and write() calls to set the baud rate on a terminal line.
For tasks like this there is a general purpose I/O control system call called ioctl():
#include <sys/ioctl.h> int ioctl(int fd, int cmd, int arg)
where fd is the appropriate file descriptor, cmd is the command function you want ioctl() to perform, and arg is an optional parameter to be used by the emd. In fact, the arg parameter can be any 4-byte-sized thing, typically an int or a pointer to a block of memory (usually a struct) which contains any parameter data required by the specified cmd.
The ioctl() call is available for use on many I/O devices, not just terminals. As a consequence, each kind of I/O device that uses ioctl() has its own set of cmd and avg parameter values to pass into the call. Because the POSIX committee felt it to have become overworked, the ioctl() call has been replaced for terminal I/O in the POSIX. 1 standard by a collection of separate functions.
In general, Linux tries to be compatible with as many different flavors of UNIX as possible. There is always the overriding consideration that in the case of a direct conflict between systems, POSIX compatibility comes first and, after that, whichever option seems most popular by common usage.
In the case of terminal I/O it has proved possible to offer the POSIX. 1 facilities as well as the ioctl() system call because the underlying data structures that hold the basic information about terminal characteristics are essentially the same for the two systems.
In Linux there is a data structure called struct termios which is accessed via <termios.h> (for compatibility struct termio is also available accessed via <termio.h>). These structures have the following basic layout:
#define NCCS 19 struct termios { tcflag_t c_iflag; /* input mode flags */ tcflag_t c_oflag; /* output mode flags */ tcflag_t c_cflag; /* control mode flags */ tcflag_t c_lflag; /* local mode flags */ cc_t c_line; /* line discipline */ cc_t c_cc[NCCS]; /* control characters */ };
One of these structures is associated with each terminal device, and the sets of flags contained in the structure control the various characteristics of the terminal interface.
The main functions performed by each of the sets of flags in the termios structure are:
The symbolic constants for all the individual flags in a struct termios are accessed via <termios.h>. This file includes <linux/termios.h>, which is where the constants are actually defined.
The main POSIX functions for accessing and modifying the contents of a termios structure are:
#include <termios h> int tcgetattr(int fd, struct termios *tptr) int tcsetattr(int fd, int action, struct termios *tptr)
where fd is the appropriate file descriptor and tptr is a pointer to a termios structure which is filled from the terminal structure by tcgetattr(), and which is used to fill the terminal structure by tcsetattr().
The possible values of the action parameter to the tcsetattr() function and their meanings are:
TCSANOW | fill the terminal stucture immediately; |
TCSADRAIN | fill the terminal structure after the current output buffer contents have been sent to the terminal; |
TCSAFLUSH | same as TCSADRAIN but also discard any unread input buffer contents. |
The same functionality can be obtained by using the TCGETA and TCSETA cmd values to the ioctl() call, using a pointer to a struct termio as its third parameter.
The following code example is a pair of simple functions to enable you to switch the standard input and output (if it is a terminal) into 'scan' mode and back (i.e. with echo off, signals off, canonical mode disabled and set to give immediate character returns from read 0):
#include <termios.h> struct termios tsave; void scan_mode(void) { struct termios tbuf; if (!isatty(O)) fatal("standard input is not a terminal"); if (tcgetattr(0, &tbuf)==-1) fatal("getting terminal attributes"); tsave = tbuf; tbuf.c_lflag &= ~(ECHO | ICANON | ISIG); tbuf.c_cc[VMIN] = tbuf.c_cc[VTIME] = 0; if (tcsetattr(0, TCSANOW, &tbuf)==-1) fatal("setting terminal attributes"); } void restore_mode(void) { if (tcsetattr(0, TCSANOW, &tsave)==-1) fatal("restoring terminal attributes"); }
Notice the use of the isatty() function within scan_node(). The isatty() function has the prototype:
#include <unistd.h> int isatty(int fd);
where fd is a file descriptor. If fd is associated with an open terminal then isatty() returns 1, otherwise it returns 0.
There is a standard command available which will allow you full control over all the flags in the ternios structure; it is called stty and its manual page gives a full list of the flags and their use. For example, the scan_mode() settings in the previous code could be set up with an stty command as follows:
$ stty -echo -icanon -isig mm 0 time 0
Every process is a member of a process group and every process group is a member of a session. In every process group there is one process whose process ID (PlO) is the same as its process group ID (PGID). This process is the leader of the process group to which it belongs. Similarly, each session has a single process which acts as the session leader (see Figure 1).
The processes in a session, via the session leader, can be associated with a terminal which will act as the session's controlling terminal. A session can only have one controlling terminal and any terminal can only control at most one session. If a session has a controlling terminal then this is the terminal from which keyboard generated signals (like Ctrl-c, Ctrl-\, etc.) can be sent to processes in the session.
Within a session, one of the process groups will be the foreground process group and any other process groups within the session will be background process groups. Only the processes within the foreground process group are allowed to take input from the session's controlling terminal. Controlling terminal output can also be restricted to this process group.
This all sounds very complicated but these things are all set up automatically for you when you login and, generally, there is no reason why you would want to change them.
If you really need to set up your own sessions and process groups, then there are two functions available for this purpose. The first one, called setsid(), allows you to set up your own sessions:
#include <unistd.h> #include <sys/types.h> pid_t setsid(void);
A successful call to setsid() creates a new session, which contains a new process group, which in turn contains the current process as both the new session and process group leader. However, setsid() cannot be called successfully by a process which is already a process group leader. We'll look at the solution to this problem in a later tutorial.
As a terminal cannot be a controlling terminal for more than one session, then a side effect of setsid() is that the new session will not have a controlling terminal because, even if it had one before, it would have been left behind with the old session.
If you want the new session to have a controlling terminal then all you do is get the session leader to open() a terminal which isn't already the controlling terminal for another session and your new session will automatically acquire the terminal as its controlling terminal.
If the new session needs to open() a terminal, but you don't want the session to acquire the terminal as its controlling terminal, then you should use the O_NOCTTY flag in the open() call.
The second function, which allows you to manipulate process groups, is called setpgid(). In general, a process can only change its own PGID or the PGID of one of its child processes. The prototype for setpgid() is:
#include <unistd.h> #include <sys/types.h> pid_t setpgid(pid_t pid, pid_t pgid);
The parameters pid and pgid are obviously a process ID and a process group ID respectively. There are three cases to consider for the values of these parameters:
For compatibility with other flavors of UNIX, another call is also provided which can change the process group of the calling process:
#include <unistd.h> int setpgrp(void);
Under Linux the setpgrp() call is exactly equivalent to the setpgid() call:
setpgid(O, 0);