This chapter provides an introduction to programming considerations for input and output handling and the input and output handling (I/O) subroutines.
The input and output (I/O) library subroutines can send data to or from either devices or files. The system treats devices as if they were I/O files. For example, you must also open and close a device just as you do a file.
Some of the subroutines use standard input and standard output as their input and output channels. For most of the subroutines, however, you can specify a different file for the source or destination of the data transfer. For some subroutines, you can use a file pointer to a structure that contains the name of the file; for others, you can use a file descriptor (that is, the positive integer assigned to the file when it is opened).
The I/O subroutines stored in the C Library (libc.a) provide stream I/O. To access these stream I/O subroutines, you must include the stdio.h file using the following statement:
#include <stdio.h>
Some of the I/O library subroutines are macros defined in a header file and some are object modules of functions. In many cases, the library contains a macro and a function that do the same type of operation. Consider the following when deciding whether to use the macro or the function:
The files, commands, and subroutines used in I/O handling provide the following interfaces:
Low-level | Basic open and close functions for files and devices. For more information, see Low-Level I/O Interfaces. |
Stream | Read and write I/O for pipes and FIFOs. For more information, see Stream I/O Interfaces. |
Terminal | Formatted output and buffering. For more information, see Terminal I/O Interfaces. |
Asynchronous | Concurrent I/O and processing. For more information, see Asynchronous I/O Interfaces. |
Input Language | The lex and yacc commands generate a lexical analyzer and a parser program for interpreting I/O. For more information, see Creating an Input Language with the lex and yacc Commands. |
Low-level I/O interfaces are direct entry points into a kernel, providing functions such as opening files, reading to and writing from files, and closing files.
The line command provides the interface that allows one line from standard input to be read and the following subroutines provide other low-level I/O functions:
open, openx, or creat | Prepare a file, or other path object, for reading and writing by means of an assigned file descriptor |
read, readx, readv, or readvx | Read from an open file descriptor |
write, writex, writev, or writevx | Write to an open file descriptor |
close | Relinquish a file descriptor |
The open and creat subroutines set up entries in three system tables. A file descriptor indexes the first table, which functions as a per process data area that can be accessed by read and write subroutines. Each entry in this table has a pointer to a corresponding entry in the second table.
The second table is a per-system data base, or file table, that allows an open file to be shared among several processes. The entries in this table indicate if the file was open for reading, writing, or as a pipe, and when the file was closed. There is also an offset to indicate where the next read or write will take place and a final pointer to indicates entry to the third table, which contains a copy of the file's i-node.
The file table contains entries for every instance of an open or create subroutine on the file, but the i-node table contains only one entry for each file.
When a read or write operation takes place, the user's arguments and the file table entry are used to set up the following variables:
If the file referred to is a character-type special file, the appropriate read or write subroutine is called to transfer data and update the count and current location. Otherwise, the current location is used to calculate a logical block number in the file.
If the file is an ordinary file, the logical block number must be mapped to a physical block number. A block-type special file need not be mapped. The resulting physical block number is used to read or write the appropriate device.
Block device drivers can provide the ability to transfer information directly between the user's core image and the device in blocks as large as the caller requests without using buffers. The method involves setting up a character-type special file corresponding to the raw device and providing read and write subroutines to create a private, non-shared buffer header with the appropriate information. If desired, separate open and close subroutines can be provided, and a special-function subroutine can be called for magnetic tape.
Stream I/O interfaces provide data as a stream of bytes that is not interpreted by the system, which offers more efficient implementation for networking protocols than character I/O processing. There are no record boundaries when reading and writing using stream I/O. For example, a process reading 100 bytes from a pipe cannot tell if the process that wrote the data into the pipe did a single write of 100 bytes, or two writes of 50 bytes, or even if the 100 bytes came from two different processes.
Stream I/Os can be pipes or FIFOs, first in, first out files. FIFOs are similar to pipes because they allow the data to flow only one way (left to right). However, a FIFO can be given a name and can be accessed by unrelated processes, unlike a pipe. FIFOs are sometimes referred to as named pipes. Because it has a name, a FIFO can be opened using the standard I/O fopen subroutine. To open a pipe, you must call the pipe subroutine, which returns a file descriptor, and the standard I/O fdopen subroutine to associate an open file descriptor with a standard I/O stream.
Stream I/O interfaces are accessed through the following subroutines and macros:
fclose | Closes a stream |
feof, ferror, clearerr, or fileno | Check the status of a stream |
fflush | Write all currently buffered characters from a stream |
fopen, freopen, or fdopen | Open a stream |
fread or fwrite | Perform binary input |
fseek, rewind, ftell, fgetpos, or fsetpos | Reposition the file pointer of a stream |
getc, fgetc, getchar, or getw | Get a character or word from an input stream |
gets or fgets | Get a string from a stream |
getwc, fgetwc, or getwchar | Get a wide character from an input stream |
getws or fgetws | Get a string from a stream |
printf, fprintf, sprintf, wsprintf, vprintf, vfprintf, vsprintf, or vwsprintf | |
Print formatted output | |
putc, putchar, fputc, or putw | Write a character or a word to a stream |
puts or fputs | Write a string to a stream |
putwc, putwchar, or fputwc | Write a character or a word to a stream |
putws or fputws | Write a wide character string to a stream |
scanf, fscanf, sscanf, or wsscanf | Convert formatted input |
setbuf, setvbuf, setbuffer, or setlinebuf | Assign buffering to a stream |
ungetc or ungetwc | Push a character back into the input stream |
Terminal I/O interfaces operate between a process and the kernel, providing functions such as buffering and formatted output.
Every terminal and pseudo-terminal has a tty structure that contains the current process group ID. This field identifies the process group to receive the signals associated with the terminal.
Terminal I/O interfaces can be accessed through the iostat command, which monitors I/O system device loading, and the uprintfd daemon, which allows kernel messages to be written to the system console.
Terminal characteristics can be enabled or disabled through the following subroutines:
Asynchronous I/O subroutines allow a process to start an I/O operation and have the subroutine return immediately after the operation is started or queued. Another subroutine is required to wait for the operation to complete (or return immediately if the operation is already finished). This means that a process can overlap its execution with its I/O or overlap I/O between different devices. Although asynchronous I/O does not significantly improve performance for a process that is reading from a disk file and writing to another disk file, asynchronous I/O provides significant performance improvements for other types of I/O driven programs, such as programs that dump a disk to a magnetic tape or display an image on an image display.
Although not required, a process performing asynchronous I/O can tell the kernel to notify it when a specified descriptor is ready for I/O (also called signal-driven I/O). When using LEGACY AIO, the kernel notifies the user process with the SIGIO signal. When using POSIX AIO, the sigevent structure is used by the programmer to determine which signal for the kernel to use to notify the user process. Signals include SIGIO, SIGUSR1, and SIGUSR2.
To use asynchronous I/O, a process must perform the following steps:
The following asynchronous I/O subroutines are provided:
aio_cancel | Cancels one or more outstanding asynchronous I/O requests |
aio_error | Retrieves the error status of an asynchronous I/O request |
aio_fsync | Synchronizes asynchronous files. |
aio_nwait | Suspends the calling process until a certain number of asynchronous I/O requests are completed. |
aio_read | Reads asynchronously from a file descriptor |
aio_return | Retrieves the return status of an asynchronous I/O request |
aio_suspend | Suspends the calling process until one or more asynchronous I/O requests is completed |
aio_write | Writes asynchronously to a file descriptor |
lio_listio | Initiates a list of asynchronous I/O requests with a single call |
poll or select | Check I/O status of multiple file descriptors and message queues |
For use with the poll subroutine, the following header files are supplied:
poll.h | Defines the structures and flags used by the poll subroutine |
aio.h | Defines the structure and flags used by the aio_read, aio_write, and aio_suspend subroutines |