Input and output handling

This topic provides an introduction to programming considerations for input and output handling and the input and output (I/O) handling subroutines.

The 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 I/O 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 by 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:

  • You cannot set a breakpoint for a macro using the dbx program.
  • Macros are usually faster than their equivalent functions because the preprocessor replaces the macros with actual lines of code in the program.
  • Macros result in larger object code after being compiled.
  • Functions can have side effects to avoid.

The files, commands, and subroutines used in I/O handling provide the following interfaces:

Low-level
The low-level interface provides basic open and close functions for files and devices.
Stream
The stream interface provides read and write I/O for pipes and FIFOs.
Terminal
The terminal interface provides formatted output and buffering.
Asynchronous
The asynchronous interface provides concurrent I/O and processing.
Input Language
The input language interface uses the lex and yacc commands to generate a lexical analyzer and a parser program for interpreting I/O.

Low-level I/O interfaces

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 database, 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.

Note: While processing an open or creat subroutine for a special file, the system always calls the device's open subroutine to allow any special processing (such as rewinding a tape or turning on a data-terminal-ready modem lead). However, the system uses the close subroutine only when the last process closes the file (that is, when the i-node table entry is deallocated). This means that a device cannot maintain or depend on a count of its users unless an exclusive-use device (that prevents a device from being reopened before its closed) is implemented.

When a read or write operation occurs, the user's arguments and the file table entry are used to set up the following variables:

  • User address of the I/O target area
  • Byte count for the transfer
  • Current location in the file

If the file referred to is a character-type special file, the appropriate read or write subroutine is called to transfer data, as well as 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 block sizes 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. Separate open and close subroutines can be provided, and a special-function subroutine can be called for magnetic tape.

Stream I/O interfaces

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. No record boundaries exist when reading and writing using stream I/O. For example, a process reading 100 bytes from a pipe cannot determine 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.

Note: Stream I/O interfaces buffer data at the user level and cannot write the data until the fclose or fflush Subroutine is performed, which might lead to unexpected results when mixed with file I/O such as read() or write().

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

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:

cfgetospeed, cfsetospeed, cfgetispeed, or cfsetispeed
Get and set input and output baud rates
ioctl
Performs control functions associated with the terminal
termdef
Queries terminal characteristics
tcdrain
Waits for output to complete
tcflow
Performs flow control functions
tcflush
Discards data from the specified queue
tcgetpgrp
Gets foreground process group ID
tcsendbreak
Sends a break on an asynchronous serial data line
tcsetattr
Sets terminal state
ttylock, ttywait, ttyunlock, or ttylocked
Control tty locking functions
ttyname or isatty
Get the name of a terminal
ttyslot
Finds the slot in the utmp file for the current user

Asynchronous I/O interfaces

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 can provide 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 that is 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:

  1. Establish a handler for the SIGIO signal. This step is necessary only if notification by the signal is requested.
  2. Set the process ID or the process group ID to receive the SIGIO signals. This step is necessary only if notification by the signal is requested.
  3. Enable asynchronous I/O. The system administrator usually determines whether asynchronous I/O is loaded (enabled). Enabling occurs at system startup.

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