A peripheral device is piece of hardware accessed by a computer system. Common peripheral devices include disks, tapes, CD-ROMs, screens, keyboards, printers, mouse devices and network interfaces. User programs perform control and I/O to these devices through system calls to operating system modules called device drivers. A device driver hides the details of device operation and protects the device from unauthorized use. Devices of the same type may vary substantially in their operation, so to be usable, even a single-user machine needs device drivers. Some operating systems provide pseudodevice drivers to simulate devices such as terminals. Pseudoterminals, for example, simplify the handling of remote login to computer systems over a network or a modem line.
Some operating systems provide specific system calls for each type of supported device, requiring the systems programmer to learn a complex set of calls for device control. UNIX has greatly simplified the programmer device interface by providing uniform access to most devices through five functions—open, close, read, write and ioctl. All devices are represented by files, called special files, that are located in the /dev directory. Thus, disk files and other devices are named and accessed in the same way. A regular file is just an ordinary data file on disk. A block special file represents a device with characteristics similar to a disk. The device driver transfers information from a block special device in blocks or chunks, and usually such devices support the capability of retrieving a block from anywhere on the device. A character special file represents a device with characteristics similar to a terminal. The device appears to represent a stream of bytes that must be accessed in sequential order.
Reading and Writing
UNIX provides sequential access to files and other devices through the read and write functions. The read function attempts to retrieve nbyte bytes from the file or device represented by fildes into the user variable buf. You must actually provide a buffer that is large enough to hold nbyte bytes of data. (A common mistake is to provide an uninitialized pointer, buf, rather than an actual buffer.)
SYNOPSIS
#include
ssize_t read(int fildes, void *buf, size_t nbyte);
POSIX
If successful, read returns the number of bytes actually read. If unsuccessful, read returns –1 and sets errno. The following table lists the mandatory errors for read.
errno
cause
ECONNRESET
read attempted on a socket and connection was forcibly closed by its peer
EAGAIN
O_NONBLOCK is set for file descriptor and thread would be delayed
EBADF
fildes is not a valid file descriptor open for reading
EINTR
read was terminated due to receipt of a signal and no data was transferred
EIO
process is a member of a background process group attempting to read from its controlling terminal and either process is ignoring or blocking SIGTTIN or process group is orphaned
ENOTCONN
read attempted on socket that is not connected
EOVERFLOW
the file is a regular file, nbyte is greater than 0, and the starting position exceeds offset maximum
ETIMEDOUT
read attempted on socket and transmission timeout occurred
EWOULDBLOCK
file descriptor is for socket marked O_NONBLOCK and no data is waiting to be received (EAGAIN is alternative)
A read operation for a regular file may return fewer bytes than requested if, for example, it reached end-of-file before completely satisfying the request. A read operation for a regular file returns 0 to indicate end-of-file. When special files corresponding to devices are read, the meaning of a read return value of 0 depends on the implementation and the particular device. A read operation for a pipe returns as soon as the pipe is not empty, so the number of bytes read can be less than the number of bytes requested. (Pipes are a type of communication buffer discussed in Chapter 6.) When reading from a terminal, read returns 0 when the user enters an end-of-file character. On many systems the default end-of-file character is Ctrl-D.
The ssize_t data type is a signed integer data type used for the number of bytes read, or –1 if an error occurs. On some systems, this type may be larger than an int. The size_t is an unsigned integer data type for the number of bytes to read.
Example 4.1
The following code segment reads at most 100 bytes into buf from standard input.
char buf[100];
ssize_t bytesread;
bytesread = read(STDIN_FILENO, buf, 100);
This code does no error checking.
The file descriptor, which represents a file or device that is open, can be thought of as an index into the process file descriptor table. The file descriptor table is in the process user area and provides access to the system information for the associated file or device.
When you execute a program from the shell, the program starts with three open streams associated with file descriptors STDIN_FILENO, STDOUT_FILENO and STDERR_FILENO. STDIN_FILENO and STDOUT_FILENO are standard input and standard output, respectively. By default, these two streams usually correspond to keyboard input and screen output. Programs should use STDERR_FILENO, the standard error device, for error messages and should never close it. In legacy code standard input, standard output and standard error are represented by 0, 1 and 2, respectively. However, you should always use their symbolic names rather than these numeric values. Section 4.6 explains how file descriptors work.
Exercise 4.2
What happens when the following code executes?
char *buf;
ssize_t bytesread;
bytesread = read(STDIN_FILENO, buf, 100);
Answer:
The code segment, which may compile without error, does not allocate space for buf. The result of read is unpredictable, but most probably it will generate a memory access violation. If buf is an automatic variable stored on the stack, it is not initialized to any particular value. Whatever that memory happens to hold is treated as the address of the buffer for reading.
The readline function of Program 4.1 reads bytes, one at a time, into a buffer of fixed size until a newline character ('\n') or an error occurs. The function handles end-of-file, limited buffer size and interruption by a signal. The readline function returns the number of bytes read or –1 if an error occurs. A return value of 0 indicates an end-of-file before any characters were read. A return value greater than 0 indicates the number of bytes read. In this case, the buffer contains a string ending in a newline character. A return value of –1 indicates that errno has been set and one of the following errors occurred.
An error occurred on read.
At least one byte was read and an end-of-file occurred before a newline was read.
nbytes-1 bytes were read and no newline was found.
Upon successful return of a value greater than 0, the buffer contains a string ending in a newline character. If readline reads from a file that does not end with a newline character, it treats the last line read as an error. The readline function is available in the restart library, of Appendix B.
Program 4.1 readline.c
The readline function returns the next line from a file.
#include
#include
int readline(int fd, char *buf, int nbytes) {
int numread = 0;
int returnval;
while (numread < nbytes - 1) {
returnval = read(fd, buf + numread, 1);
if ((returnval == -1) && (errno == EINTR))
continue;
if ( (returnval == 0) && (numread == 0) )
return 0;
if (returnval == 0)
break;
if (returnval == -1)
return -1;
numread++;
if (buf[numread-1] == '\n') {
buf[numread] = '\0';
return numread;
}
}
errno = EINVAL;
return -1;
}
Example 4.3
The following code segment calls the readline function of Program 4.1 to read a line of at most 99 bytes from standard input.
int bytesread;
char mybuf[100];
bytesread = readline(STDIN_FILENO, mybuf, sizeof(mybuf));
Exercise 4.4
Under what circumstances does the readline function of Program 4.1 return a buffer with no newline character?
Answer:
This can only happen if the return value is 0 or –1. The return value of 0 indicates that nothing was read. The return of –1 indicates some type of error. In either case, the buffer may not contain a string.
The write function attempts to output nbyte bytes from the user buffer buf to the file represented by file descriptor fildes.
SYNOPSIS
#include
ssize_t write(int fildes, const void *buf, size_t nbyte);
POSIX
If successful, write returns the number of bytes actually written. If unsuccessful, write returns –1 and sets errno. The following table lists the mandatory errors for write.
errno
cause
ECONNRESET
write attempted on a socket that is not connected
EAGAIN
O_NONBLOCK is set for file descriptor and thread would be delayed
EBADF
fildes is not a valid file descriptor open for writing
EFBIG
attempt to write a file that exceeds implementation-defined maximum; file is a regular file, nbyte is greater than 0, and starting position exceeds offset maximum
EINTR
write was terminated due to receipt of a signal and no data was transferred
EIO
process is a member of a background process group attempting to write to controlling terminal, TOSTOP is set, process is neither blocking nor ignoring SIGTTOU and process group is orphaned
ENOSPC
no free space remaining on device containing the file
EPIPE
attempt to write to a pipe or FIFO not open for reading or that has only one end open (thread may also get SIGPIPE), or write attempted on socket shut down for writing or not connected (if not connected, also generates SIGPIPE signal)
EWOULDBLOCK
file descriptor is for socket marked O_NONBLOCK and write would block (EAGAIN is alternative)
Exercise 4.5
What can go wrong with the following code segment?
#define BLKSIZE 1024
char buf[BLKSIZE];
read(STDIN_FILENO, buf, BLKSIZE);
write(STDOUT_FILENO, buf, BLKSIZE);
Answer:
The write function assumes that the read has filled buf with BLKSIZE bytes. However, read may fail or may not read the full BLKSIZE bytes. In these two cases, write outputs garbage.
Exercise 4.6
What can go wrong with the following code segment to read from standard input and write to standard output?
#define BLKSIZE 1024
char buf[BLKSIZE];
ssize_t bytesread;
bytesread = read(STDIN_FILENO, buf, BLKSIZE);
if (bytesread > 0)
write(STDOUT_FILE, buf, bytesread);
Answer:
Although write uses bytesread rather than BLKSIZE, there is no guarantee that write actually outputs all of the bytes requested. Furthermore, either read or write can be interrupted by a signal. In this case, the interrupted call returns a –1 with errno set to EINTR.
Program 4.2 copies bytes from the file represented by fromfd to the file represented by tofd. The function restarts read and write if either is interrupted by a signal. Notice that the write statement specifies the buffer by a pointer, bp, rather than by a fixed address such as buf. If the previous write operation did not output all of buf, the next write operation must start from the end of the previous output. The copyfile function returns the number of bytes read and does not indicate whether or not an error occurred.
Example 4.7 simplecopy.c
The following program calls copyfile to copy a file from standard input to standard output.
#include
#include
int copyfile(int fromfd, int tofd);
int main (void) {
int numbytes;
numbytes = copyfile(STDIN_FILENO, STDOUT_FILENO);
fprintf(stderr, "Number of bytes copied: %d\n", numbytes);
return 0;
}
Exercise 4.8
What happens when you run the program of Example 4.7?
Answer:
Standard input is usually set to read one line at a time, so I/O is likely be entered and echoed on line boundaries. The I/O continues until you enter the end-of-file character (often Ctrl-D by default) at the start of a line or you interrupt the program by entering the interrupt character (often Ctrl-C by default). Use the stty -a command to find the current settings for these characters.
Program 4.2 copyfile1.c
The copyfile.c function copies a file from fromfd to tofd.
#include
#include
#define BLKSIZE 1024
int copyfile(int fromfd, int tofd) {
char *bp;
char buf[BLKSIZE];
int bytesread, byteswritten;
int totalbytes = 0;
for ( ; ; ) {
while (((bytesread = read(fromfd, buf, BLKSIZE)) == -1) &&
(errno == EINTR)) ; /* handle interruption by signal */
if (bytesread <= 0) /* real error or end-of-file on fromfd */
break;
bp = buf;
while (bytesread > 0) {
while(((byteswritten = write(tofd, bp, bytesread)) == -1 ) &&
(errno == EINTR)) ; /* handle interruption by signal */
if (byteswritten <= 0) /* real error on tofd */
break;
totalbytes += byteswritten;
bytesread -= byteswritten;
bp += byteswritten;
}
if (byteswritten == -1) /* real error on tofd */
break;
}
return totalbytes;
}
Exercise 4.9
How would you use the program of Example 4.7 to copy the file myin.dat to myout.dat?
Answer:
Use redirection. If the executable of Example 4.7 is called simplecopy, the line would be as follows.
simplecopy < myin.dat > myout.dat
The problems of restarting read and write after signals and of writing the entire amount requested occur in nearly every program using read and write. Program 4.3 shows a separate r_read function that you can use instead of read when you want to restart after a signal. Similarly, Program 4.4 shows a separate r_write function that restarts after a signal and writes the full amount requested. For convenience, a number of functions, including r_read, r_write, copyfile and readline, have been collected in a library called restart.c. The prototypes for these functions are contained in restart.h, and we include this header file when necessary. Appendix B presents the complete restart library implementation.
Program 4.3 r_read.c
The r_read.c function is similar to read except that it restarts itself if interrupted by a signal.
#include
#include
ssize_t r_read(int fd, void *buf, size_t size) {
ssize_t retval;
while (retval = read(fd, buf, size), retval == -1 && errno == EINTR) ;
return retval;
}
Program 4.4 r_write.c
The r_write.c function is similar to write except that it restarts itself if interrupted by a signal and writes the full amount requested.
#include
#include
ssize_t r_write(int fd, void *buf, size_t size) {
char *bufp;
size_t bytestowrite;
ssize_t byteswritten;
size_t totalbytes;
for (bufp = buf, bytestowrite = size, totalbytes = 0;
bytestowrite > 0;
bufp += byteswritten, bytestowrite -= byteswritten) {
byteswritten = write(fd, bufp, bytestowrite);
if ((byteswritten) == -1 && (errno != EINTR))
return -1;
if (byteswritten == -1)
byteswritten = 0;
totalbytes += byteswritten;
}
return totalbytes;
}
The functions r_read and r_write can greatly simplify programs that need to read and write while handling signals.
Program 4.5 shows the readwrite function that reads bytes from one file descriptor and writes all of the bytes read to another one. It uses a buffer of size PIPE_BUF to transfer at most PIPE_BUF bytes. This size is useful for writing to pipes since a write to a pipe of PIPE_BUF bytes or less is atomic. Program 4.6 shows a version of copyfile that uses the readwrite function. Compare this with Program 4.2.
Program 4.5 readwrite.c
A program that reads from one file descriptor and writes all the bytes read to another file descriptor.
#include
#include "restart.h"
#define BLKSIZE PIPE_BUF
int readwrite(int fromfd, int tofd) {
char buf[BLKSIZE];
int bytesread;
if ((bytesread = r_read(fromfd, buf, BLKSIZE)) == -1)
return -1;
if (bytesread == 0)
return 0;
if (r_write(tofd, buf, bytesread) == -1)
return -1;
return bytesread;
}
Program 4.6 copyfile.c
A simplified implementation of copyfile that uses r_read and r_write.
#include
#include "restart.h"
#define BLKSIZE 1024
int copyfile(int fromfd, int tofd) {
char buf[BLKSIZE];
int bytesread, byteswritten;
int totalbytes = 0;
for ( ; ; ) {
if ((bytesread = r_read(fromfd, buf, BLKSIZE)) <= 0)
break;
if ((byteswritten = r_write(tofd, buf, bytesread)) == -1)
break;
totalbytes += byteswritten;
}
return totalbytes;
}
The r_write function writes all the bytes requested and restarts the write if fewer bytes are written. The r_read only restarts if interrupted by a signal and often reads fewer bytes than requested. The readblock function is a version of read that continues reading until the requested number of bytes is read or an error occurs. Program 4.7 shows an implementation of readblock. The readblock function is part of the restart library. It is especially useful for reading structures.
Program 4.7 readblock.c
A function that reads a specific number of bytes.
#include
#include
ssize_t readblock(int fd, void *buf, size_t size) {
char *bufp;
size_t bytestoread;
ssize_t bytesread;
size_t totalbytes;
for (bufp = buf, bytestoread = size, totalbytes = 0;
bytestoread > 0;
bufp += bytesread, bytestoread -= bytesread) {
bytesread = read(fd, bufp, bytestoread);
if ((bytesread == 0) && (totalbytes == 0))
return 0;
if (bytesread == 0) {
errno = EINVAL;
return -1;
}
if ((bytesread) == -1 && (errno != EINTR))
return -1;
if (bytesread == -1)
bytesread = 0;
totalbytes += bytesread;
}
return totalbytes;
}
There are only three possibilities for the return value of readblock. The readblock function returns 0 if an end-of-file occurs before any bytes are read. This happens if the first call to read returns 0. If readblock is successful, it returns size, signifying that the requested number of bytes was successfully read. Otherwise, readblock returns –1 and sets errno. If readblock reaches the end-of-file after some, but not all, of the needed bytes have been read, readblock returns –1 and sets errno to EINVAL.
Example 4.10
The following code segment can be used to read a pair of integers from an open file descriptor.
struct {
int x;
int y;
} point;
if (readblock(fd, &point, sizeof(point)) <= 0)
fprintf(stderr, "Cannot read a point.\n");
Program 4.8 combines readblock with r_write to read a fixed number of bytes from one open file descriptor and write them to another open file descriptor.
Program 4.8 readwriteblock.c
A program that copies a fixed number of bytes from one file descriptor to another.
#include "restart.h"
int readwriteblock(int fromfd, int tofd, char *buf, int size) {
int bytesread;
bytesread = readblock(fromfd, buf, size);
if (bytesread != size) /* can only be 0 or -1 */
return bytesread;
return r_write(tofd, buf, size);
}