B.3 Vector Reads and Writes

The write call takes as arguments a pointer to the start of a buffer of data and the length of that buffer. It writes a contiguous region of memory to the file descriptor. However, a program often will need to write several items of data, each residing at a different part of memory. To use write, the program either will have to copy the items into a single memory region, which obviously makes inefficient use of CPU cycles and memory, or will have to make multiple calls to write.

For some applications, multiple calls to write are inefficient or undesirable. For example, when writing to a network socket, two calls to write may cause two packets to be sent across the network, whereas the same data could be sent in a single packet if a single call to write were possible.

The writev call enables you to write multiple discontiguous regions of memory to a file descriptor in a single operation. This is called a vector write. The cost of using writev is that you must set up a data structure specifying the start and length of each region of memory. This data structure is an array of struct iovec elements. Each element specifies one region of memory to write; the fields iov_base and iov_len specify the address of the start of the region and the length of the region, respectively. If you know ahead of time how many regions you'll need, you can simply declare a struct iovec array variable; if the number of regions can vary, you must allocate the array dynamically.

Call writev passing a file descriptor to write to, the struct iovec array, and the number of elements in the array. The return value is the total number of bytes written.

The program in Listing B.7 writes its command-line arguments to a file using a single writev call. The first argument is the name of the file; the second and subsequent arguments are written to the file of that name, one on each line. The program allocates an array of struct iovec elements that is twice as long as the number of arguments it is writing—for each argument it writes the text of the argument itself as well as a new line character. Because we don't know the number of arguments in advance, the array is allocated using malloc.

Listing B.7 (write-args.c) Write the Argument List to a File with writev
#include <fcntl.h> 
#include <stdlib.h> 
#include <sys/stat.h> 
#include <sys/types.h> 
#include <sys/uio.h> 
#include <unistd.h> 
 
int main (int argc, char* argv[]) 
{
  int fd; 
  struct iovec* vec; 
  struct iovec* vec_next; 
  int i; 
  /* We'll need a "buffer" containing a newline character. Use an 
     ordinary char variable for this.  */ 
  char newline = '\n'; 
  /* The first command-line argument is the output filename.  */ 
  char* filename = argv[1]; 
  /* Skip past the first two elements of the argument list. Element 
     0 is the name of this program, and element 1 is the output 
     filename.  */ 
  argc -= 2; 
  argv += 2; 
 
  /* Allocate an array of iovec elements. We'll need two for each 
     element of the argument list, one for the text itself, and one for 
     a newline. */ 
  vec = (struct iovec*) malloc (2 * argc * sizeof (struct iovec)); 
 
  /* Loop over the argument list, building the iovec entries.  */ 
  vec_next = vec; 
  for (i = 0; i < argc; ++i) {
    /* The first element is the text of the argument itself.  */ 
    vec_next->iov_base = argv[i]; 
    vec_next->iov_len = strlen (argv[i]); 
    ++vec_next; 
    /* The second element is a single newline character. It's okay for 
       multiple elements of the struct iovec array to point to the 
       same region of memory.  */ 
    vec_next->iov_base = &newline; 
    vec_next->iov_len = 1; 
    ++vec_next; 
  } 
 
  /* Write the arguments to a file.  */ 
  fd = open (filename, O_WRONLY | O_CREAT); 
  writev (fd, vec, 2 * argc); 
  close (fd); 
  free (vec); 
  return 0; 
} 

Here's an example of running write-args.

 
% ./write-args outputfile "first arg" "second arg" "third arg" 
% cat outputfile 
first arg 
second arg 
third arg 

Linux provides a corresponding function readv that reads in a single operation into multiple discontiguous regions of memory. Similar to writev, an array of struct iovec elements specifies the memory regions into which the data will be read from the file descriptor.