Team BBL
Previous Page Next Page

Chapter 14

14.1

The test program is shown in Figure C.14

Figure C.14. Determine record-locking behavior
#include "apue.h"
#include <fcntl.h>
#include <errno.h>

void
sigint(int signo)
{
}

int
main(void)
{
    pid_t pid1, pid2, pid3;
    int fd;

    setbuf(stdout, NULL);
    signal_intr(SIGINT, sigint);

    /*
     * Create a file.
     */
    if ((fd = open("lockfile", O_RDWR|O_CREAT, 0666)) < 0)
        err_sys("can't open/create lockfile");
    /*
     * Read-lock the file.
     */
    if ((pid1 = fork()) < 0) {
        err_sys("fork failed");
    } else if (pid1 == 0) { /* child */
        if (lock_reg(fd, F_SETLK, F_RDLCK, 0, SEEK_SET, 0) < 0)
            err_sys("child 1: can't read-lock file");
        printf("child 1: obtained read lock on file\n");
        pause();
        printf("child 1: exit after pause\n");
        exit(0);
    } else {        /* parent */
        sleep(2);
    }

    /*
     * Parent continues ... read-lock the file again.
     */
    if ((pid2 = fork()) < 0) {
        err_sys("fork failed");
    } else if (pid2 == 0) { /* child */
        if (lock_reg(fd, F_SETLK, F_RDLCK, 0, SEEK_SET, 0) < 0)
            err_sys("child 2: can't read-lock file");
        printf("child 2: obtained read lock on file\n");
        pause();
        printf("child 2: exit after pause\n");
        exit(0);
    } else {        /* parent */
       sleep(2);
    }

    /*
     * Parent continues ... block while trying to write-lock
     * the file.
     */
    if ((pid3 = fork()) < 0) {
        err_sys("fork failed");
    } else if (pid3 == 0) { /* child */
        if (lock_reg(fd, F_SETLK, F_WRLCK, 0, SEEK_SET, 0) < 0)
            printf("child 3: can't set write lock: %s\n",
              strerror(errno));
        printf("child 3 about to block in write-lock...\n");
        if (lock_reg(fd, F_SETLKW, F_WRLCK, 0, SEEK_SET, 0) < 0)
            err_sys("child 3: can't write-lock file");
        printf("child 3 returned and got write lock????\n");
        pause();
        printf("child 3: exit after pause\n");
        exit(0);
    } else {        /* parent */
        sleep(2);
    }
    /*
     * See if a pending write lock will block the next
     * read-lock attempt.
     */
    if (lock_reg(fd, F_SETLK, F_RDLCK, 0, SEEK_SET, 0) < 0)
        printf("parent: can't set read lock: %s\n",
          strerror(errno));
    else
        printf("parent: obtained additional read lock while"
          " write lock is pending\n");
    printf("killing child 1...\n");
    kill(pid1, SIGINT);
    printf("killing child 2...\n");
    kill(pid2, SIGINT);
    printf("killing child 3...\n");
    kill(pid3, SIGINT);
    exit(0);
}

On all four systems described in this book, the behavior is the same: additional readers can starve pending writers. Running the program gives us

   child 1: obtained read lock on file
   child 2: obtained read lock on file
   child 3: can't set write lock: Resource temporarily unavailable
   child 3 about to block in write-lock...
   parent: obtained additional read lock while write lock is pending
   killing child 1...
   child 1: exit after pause
   killing child 2...
   child 2: exit after pause
   killing child 3...
   child 3: can't write-lock file: Interrupted system call

14.2

Most systems define the fd_set data type to be a structure that contains a single member: an array of long integers. One bit in this array corresponds to each descriptor. The four FD_ macros then manipulate this array of longs, turning specific bits on and off and testing specific bits.

One reason that the data type is defined to be a structure containing an array and not simply an array is to allow variables of type fd_set to be assigned to one another with the C assignment statement.

14.3

Most systems allow us to define the constant FD_SETSIZE before including the header <sys/select.h>. For example, we can write

    #define FD_SETSIZE 2048
    #include <sys/select.h>

to define the fd_set data type to accommodate 2,048 descriptors. This works on FreeBSD 5.2.1, Mac OS X 10.3, and Solaris 9. Linux 2.4.22 implements things differently.

14.4

The following table lists the functions that do similar things.

FD_ZERO

sigemptyset

FD_SET

sigaddset

FD_CLR

sigdelset

FD_ISSET

sigismember


There is not an FD_xxx function that corresponds to sigfillset. With signal sets, the pointer to the set is always the first argument, and the signal number is the second argument. With descriptor sets, the descriptor number is the first argument, and the pointer to the set is the next argument.

14.5

Up to five types of information are returned by getmsg: the data itself, the length of the data, the control information, the length of the control information, and the flags.

14.6

Figure C.15 shows an implementation using select.

Figure C.15. Implementation of sleep_us using select
#include "apue.h"
#include <sys/select.h>

void
sleep_us(unsigned int nusecs)
{
     struct timeval  tval;

     tval.tv_sec = nusecs / 1000000;
     tval.tv_usec = nusecs % 1000000;
     select(0, NULL, NULL, NULL, &tval);
}

Figure C.16 shows an implementation using poll.

Example C.16. Implementation of sleep_us using poll
#include <poll.h>

void
sleep_us(unsigned int nusecs)
{
    struct pollfd   dummy;
    int             timeout;

    if ((timeout = nusecs / 1000) <= 0)
        timeout = 1;
    poll(&dummy, 0, timeout);
}

As the BSD usleep(3) manual page states, usleep uses the setitimer interval timer and performs eight system calls each time it's called. It correctly interacts with other timers set by the calling process, and it is not interrupted if a signal is caught.

14.7

No. What we would like to do is have TELL_WAIT create a temporary file and use 1 byte for the parent's lock and 1 byte for the child's lock. WAIT_CHILD would have the parent wait to obtain a lock on the child's byte, and TELL_PARENT would have the child release the lock on the child's byte. The problem, however, is that calling fork releases all the locks in the child, so the child can't start off with any locks of its own.

14.8

A solution is shown in Figure C.17.

Figure C.17. Calculation of pipe capacity using nonlocking writes
#include "apue.h"
#include <fcntl.h>

int
main(void)
{
    int i, n;
    int fd[2];

    if (pipe(fd) < 0)
        err_sys("pipe error");
    set_fl(fd[1], O_NONBLOCK);

    /*
     * Write 1 byte at a time until pipe is full.
     */
   for (n = 0; ; n++) {
       if ((i = write(fd[1], "a", 1)) != 1) {
           printf("write ret %d, ", i);
           break;
       }
   }
   printf("pipe capacity = %d\n", n);
   exit(0);
}

The following table shows the values calculated for our four platforms.

Platform

Pipe Capacity (bytes)

FreeBSD 5.2.1

16,384

Linux 2.4.22

4,096

Mac OS X 10.3

8,192

Solaris 9

9,216


These values can differ from the corresponding PIPE_BUF values, because PIPE_BUF is defined to be the maximum amount of data that can be written to a pipe atomically. Here, we calculate the amount of data that a pipe can hold independent of any atomicity constraints.

14.10

Whether the program in Figure 14.32 updates the last-access time for the input file depends on the operating system and the type of file system on which the file resides.

    Team BBL
    Previous Page Next Page