[ Previous | Next | Table of Contents | Index | Library Home | Legal | Search ]

General Programming Concepts: Writing and Debugging Programs

Chapter 7. Input and Output Handling

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 (Low-Level I/O Interfaces) Basic open and close functions for files and devices.
Stream (Stream I/O Interfaces) Read and write I/O for pipes and FIFOs.
Terminal (Terminal I/O Interfaces) Formatted output and buffering.
Asynchronous (Asynchronous I/O Interfaces) Concurrent I/O and processing.
Input Language (Creating an Input Language with the lex and yacc Commands) The lex and yacc commands 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 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.

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 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

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

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 terminal group ID. This field identifies the process group to receive the signals associated with the terminal.

Terminal I/O interfaces are 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 terminal screen.

A daemon opens a terminal device in order to log error messages to the /dev/tty or /dev/console file. If background writes are not allowed, disassociate the daemon process from the controlling terminal.

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 open file descriptors, such as controlling the ability of background processes to produce output on the control terminal
termdef Queries terminal characteristics
tcdrain Waits for output to complete
tcflow Performs flow control functions
tcflush Discards data from the specified queue
tcgetaattr Gets terminal state
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 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). The kernel notifies the user process with the SIGIO signal.

To use asynchronous I/O, a process must perform three steps:

  1. Establish a handler for the SIGIO signal. This step is only necessary 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 only necessary 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_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

[ Previous | Next | Table of Contents | Index | Library Home | Legal | Search ]