Safari Books Online is a digital library providing on-demand subscription access to thousands of learning resources.
Now that we’ve walked through Example 4-1, let’s run it and see if we can identify its synchronization problems.
Example 4-2 presents a command-line utility designed to invoke the race_ioctl function in Example 4-1:
Example 4-2. race_config.c
#include <sys/types.h>
#include <sys/ioctl.h>
#include <err.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "../race/race_ioctl.h"
static enum {UNSET, ATTACH, DETACH, QUERY, LIST} action = UNSET;
/*
* The usage statement: race_config -a | -d unit | -q unit | -l
*/
static void
usage()
{
/*
* Arguments for this program are "either-or." For example,
* 'race_config -a' or 'race_config -d unit' are valid; however,
* 'race_config -a -d unit' is invalid.
*/
fprintf(stderr, "usage: race_config -a | -d unit | -q unit | -l\n");
exit(1);
}
/*
* This program manages the doubly linked list found in /dev/race. It
* allows you to add or remove an item, query the existence of an item,
* or print every item on the list.
*/
int
main(int argc, char *argv[])
{
int ch, fd, i, unit;
char *p;
/*
* Parse the command line argument list to determine
* the correct course of action.
*
* -a: add an item.
* -d unit: detach an item.
* -q unit: query the existence of an item.
* -l: list every item.
*/
while ((ch = getopt(argc, argv, "ad:q:l")) != -1)
switch (ch) {
case 'a':
if (action != UNSET)
usage();
action = ATTACH;
break;
case 'd':
if (action != UNSET)
usage();
action = DETACH;
unit = (int)strtol(optarg, &p, 10);
if (*p)
errx(1, "illegal unit -- %s", optarg);
break;
case 'q':
if (action != UNSET)
usage();
action = QUERY;
unit = (int)strtol(optarg, &p, 10);
if (*p)
errx(1, "illegal unit -- %s", optarg);
break;
case 'l':
if (action != UNSET)
usage();
action = LIST;
break;
default:
usage();
}
/*
* Perform the chosen action.
*/
if (action == ATTACH) {
fd = open("/dev/" RACE_NAME, O_RDWR);
if (fd < 0)
err(1, "open(/dev/%s)", RACE_NAME);
i = ioctl(fd, RACE_IOC_ATTACH, &unit);
if (i < 0)
err(1, "ioctl(/dev/%s)", RACE_NAME);
printf("unit: %d\n", unit);
close (fd);
} else if (action == DETACH) {
fd = open("/dev/" RACE_NAME, O_RDWR);
if (fd < 0)
err(1, "open(/dev/%s)", RACE_NAME);
i = ioctl(fd, RACE_IOC_DETACH, &unit);
if (i < 0)
err(1, "ioctl(/dev/%s)", RACE_NAME);
close (fd);
} else if (action == QUERY) {
fd = open("/dev/" RACE_NAME, O_RDWR);
if (fd < 0)
err(1, "open(/dev/%s)", RACE_NAME);
i = ioctl(fd, RACE_IOC_QUERY, &unit);
if (i < 0)
err(1, "ioctl(/dev/%s)", RACE_NAME);
close (fd);
} else if (action == LIST) {
fd = open("/dev/" RACE_NAME, O_RDWR);
if (fd < 0)
err(1, "open(/dev/%s)", RACE_NAME);
i = ioctl(fd, RACE_IOC_LIST, NULL);
if (i < 0)
err(1, "ioctl(/dev/%s)", RACE_NAME);
close (fd);
} else
usage();
return (0);
}
Example 4-2 is a bog-standard command-line utility. As such, I won’t cover its program structure.
The following shows an example execution of Example 4-2:
$sudo kldload ./race.koRace driver loaded. $sudo ./race_config -a & sudo ./race_config -a &[1] 2378 [2] 2379 $ unit: 0 unit: 0
Above, two threads simultaneously add a race_softc structure to race_list, which results in two race_softc structures with the “unique” unit number 0—this is a problem, yes?
Here’s another example:
$sudo kldload ./race.koRace driver loaded. $sudo ./race_config -a & sudo kldunload race.ko &[1] 2648 [2] 2649 $ unit: 0 Race driver unloaded. [1]- Done sudo ./race_config -a [2]+ Done sudo kldunload race.ko $dmesg | tail -n 1Warning: memory type race leaked memory on destroy (1 allocations, 16 bytes leaked).
Above, one thread adds a race_softc structure to race_list while another thread unloads race.ko, which causes a memory leak. Recall that MOD_QUIESCE is supposed to prevent this, but it didn’t. Why?
The problem, in both examples, is a race condition. Race conditions are errors caused by a sequence of events. In the first example, both threads check race_list simultaneously, discover that it is empty, and assign 0 as the unit number. In the second example, MOD_QUIESCE returns error-free, a race_softc structure is then added to race_list, and finally MOD_UNLOAD completes.
One characteristic of race conditions is that they’re hard to reproduce. Ergo, the results were doctored in the preceding examples. That is, I caused the threads to context switch at specific points to achieve the desired outcome. Under normal conditions, it would have taken literally millions of attempts before those race conditions would occur, and I didn’t want to spend that much time.