Advanced Programming in the UNIX® Environment, Second Edition


About the Book

Source Code

The Lost Chapter

Errata

Additional Acknowledgements

FAQs

Contact the Author

Rich Stevens home page

Buy the book from Addison-Wesley Professional

The first program will create 8 threads and set an alarm. You can control which threads block SIGALRM and which thread makes the call to alarm(2). Specifying a thread number (or "main" for the main thread) will cause that thread to block SIGALRM. Specifying "a[1-8]" will cause the specified thread to call alarm. By default, the main thread calls alarm. You can use this program to see which thread is delivered SIGALRM. Because Linux 2.4.x platforms use threads based on clone(2), results will differ from the other platforms. Linux 2.6.x platforms will behave more like the others (FreeBSD, Mac OS X, and Solaris).


#include "apue.h"
#include <pthread.h>
#include <signal.h>

#define NTHR 8

sigset_t mask;
pthread_t tid[NTHR];
int block[NTHR];
int alarmthread;

void
delay(int nsec)
{
    struct timespec ts;

    ts.tv_sec = nsec;
    ts.tv_nsec = 0;
    nanosleep(&ts, NULL);
}

void
handler(int signo)
{
    int i;
    pthread_t id;

    id = pthread_self();
    for (i = 0; i < NTHR; i++)
        if (tid[i] == id)
            break;
    if (i >= NTHR)
        printf("main thread: caught signal %d\n", signo);
    else
        printf("thread %d: caught signal %d\n", i+1, signo);
}

void *
thr_fn(void *arg)
{
    int id;

    id = (int)arg;
    printf("thread %d started...\n", id);
    if (block[id-1] != 0) {
        printf("thread %d blocking SIGALRM...\n", id);
        if (pthread_sigmask(SIG_BLOCK, &mask, NULL))
            err_sys("thread %d: can't block SIGALRM", id);
    }
    if (alarmthread == id) {
        printf("thread %d: setting alarm...\n", id);
        alarm(5);
    }
    pause();
    printf("thread %d: exiting\n", id);
    exit(id);
}

int
main(int argc, char *argv[])
{
    struct sigaction act;
    int i, err, id;
    int doblock = 0;

    setbuf(stdout, NULL);
    sigemptyset(&mask);
    sigaddset(&mask, SIGALRM);
    act.sa_handler = handler;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    for (i = 1; i < argc; i++) {
        if (strcmp(argv[i], "main") == 0) {
            doblock = 1;
        } else if (argv[i][0] == 'a') {
            alarmthread = atoi(&argv[i][1]);
            if (alarmthread > NTHR || alarmthread < 1)
                err_quit("thread ID must be between 1 and %d\n", NTHR);
        } else {
            id = atoi(argv[i]);
            if (id > NTHR || id < 1)
                err_quit("thread ID must be between 1 and %d\n", NTHR);
            block[id-1] = 1;
        }
    }
    if (sigaction(SIGALRM, &act, NULL) < 0)
        err_sys("can't install signal handler");
    for (i = 0; i < NTHR; i++) {
        err = pthread_create(&tid[i], NULL, thr_fn, (void *)(i+1));
        if (err != 0)
            err_exit(err, "can't create thread");
    }
    if (doblock != 0) {
        printf("main thread blocking SIGALRM...\n");
        if (pthread_sigmask(SIG_BLOCK, &mask, NULL))
            err_sys("main thread: can't block SIGALRM");
    }
    if (alarmthread == 0) {
        printf("main thread: setting alarm...\n");
        alarm(5);
    }
    delay(10);
    printf("main thread: return from delay\n");
    exit(0);
}
        

The second program is like the first, but generates a hardware fault instead. You can use it to verify that the thread that causes the fault is the same thread that is delivered SIGSEGV.


#include "apue.h"
#include <pthread.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/mman.h>

#if !defined(MAP_FAILED)
#define MAP_FAILED ((void *)-1)
#endif

#define NTHR 8
#define MAPSZ (8*1024*1024)

sigset_t mask;
pthread_t tid[NTHR];
int block[NTHR];
int faultthread;
char *mapaddr;

void
delay(int nsec)
{
    struct timespec ts;

    ts.tv_sec = nsec;
    ts.tv_nsec = 0;
    nanosleep(&ts, NULL);
}

void
handler(int signo)
{
    int i;
    pthread_t id;

    id = pthread_self();
    for (i = 0; i < NTHR; i++)
        if (tid[i] == id)
            break;
    if (i >= NTHR)
        printf("main thread: caught signal %d\n", signo);
    else
        printf("thread %d: caught signal %d\n", i+1, signo);
    exit(1);
}

void
oops(void)
{
    char c;

    c = *mapaddr;
}

void *
thr_fn(void *arg)
{
    int id;

    id = (int)arg;
    printf("thread %d started...\n", id);
    if (block[id-1] != 0) {
        printf("thread %d blocking SIGSEGV...\n", id);
        if (pthread_sigmask(SIG_BLOCK, &mask, NULL))
            err_sys("thread %d: can't block SIGSEGV", id);
    }
    if (faultthread == id) {
        delay(2);
        printf("thread %d: generating fault...\n", id);
        oops();
    }
    pause();
    printf("thread %d: exiting\n", id);
    exit(id);
}

int
main(int argc, char *argv[])
{
    struct sigaction act;
    int i, err, id, fd;
    int doblock = 0;

    if ((fd = open("tempfile", O_RDWR|O_CREAT)) < 0)
        err_sys("can't open %s", argv[0]);
    if (lseek(fd, MAPSZ-1, SEEK_SET) == -1)
        err_sys("can't seek");
    if (write(fd, "a", 1) != 1)
        err_sys("can't write to temp file");
    mapaddr = mmap(0, MAPSZ, PROT_READ, MAP_SHARED, fd, 0);
    if (mapaddr == MAP_FAILED)
        err_sys("can't map temp file");
    unlink("tempfile");
    close(fd);
    if (munmap(mapaddr, MAPSZ) < 0)    /* invalidate the mapping */
        err_sys("can't unmap file");
    for (i = 1; i < argc; i++) {
        if (strcmp(argv[i], "main") == 0) {
            doblock = 1;
        } else if (argv[i][0] == 'a') {
            faultthread = atoi(&argv[i][1]);
            if (faultthread > NTHR || faultthread < 1)
                err_quit("thread ID must be between 1 and %d\n", NTHR);
        } else {
            id = atoi(argv[i]);
            if (id > NTHR || id < 1)
                err_quit("thread ID must be between 1 and %d\n", NTHR);
            block[id-1] = 1;
        }
    }
    sigemptyset(&mask);
    sigaddset(&mask, SIGSEGV);
    act.sa_handler = handler;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    if (sigaction(SIGSEGV, &act, NULL) < 0)
        err_sys("can't install signal handler");
    for (i = 0; i < NTHR; i++) {
        err = pthread_create(&tid[i], NULL, thr_fn, (void *)(i+1));
        if (err != 0)
            err_exit(err, "can't create thread");
    }
    delay(2);
    if (doblock != 0) {
        printf("main thread blocking SIGSEGV...\n");
        if (pthread_sigmask(SIG_BLOCK, &mask, NULL))
            err_sys("main thread: can't block SIGSEGV");
    }
    if (faultthread == 0) {
        printf("main thread: generating fault...\n");
        oops();
    }
    delay(4);
    printf("main thread: return from delay\n");
    exit(0);
}
        

The third program is like the first, but uses setitimer(2) instead of alarm(2). You can use this to see how the signals are distributed among the threads over time. This test program was inspired by another test written by Michael Kerrisk.


#include "apue.h"
#include <pthread.h>
#include <signal.h>
#include <sys/time.h>

#define NTHR 8

sigset_t mask;
pthread_t tid[NTHR];
int block[NTHR];
int alarmthread;

void
set_timer(void)
{
    struct itimerval tv;

    tv.it_value.tv_sec = 1;
    tv.it_value.tv_usec = 0;
    tv.it_interval.tv_sec = 0;
    tv.it_interval.tv_usec = 1000;
    if (setitimer(ITIMER_REAL, &tv, NULL) < 0)
        err_sys("can't set timer");
}

void
delay(int nsec)
{
    struct timespec ts;

    ts.tv_sec = nsec;
    ts.tv_nsec = 0;
    nanosleep(&ts, NULL);
}

void
handler(int signo)
{
    int i;
    pthread_t id;

    id = pthread_self();
    for (i = 0; i < NTHR; i++)
        if (tid[i] == id)
            break;
    if (i >= NTHR)
        printf("main thread: caught signal %d\n", signo);
    else
        printf("thread %d: caught signal %d\n", i+1, signo);
}

void *
thr_fn(void *arg)
{
    int id;

    id = (int)arg;
    printf("thread %d started...\n", id);
    if (block[id-1] != 0) {
        printf("thread %d blocking SIGALRM...\n", id);
        if (pthread_sigmask(SIG_BLOCK, &mask, NULL))
            err_sys("thread %d: can't block SIGALRM", id);
    }
    if (alarmthread == id) {
        printf("thread %d: setting alarm...\n", id);
        set_timer();
    }
    for (;;)
        pause();
}

int
main(int argc, char *argv[])
{
    struct sigaction act;
    int i, err, id;
    int doblock = 0;

    setbuf(stdout, NULL);
    sigemptyset(&mask);
    sigaddset(&mask, SIGALRM);
    act.sa_handler = handler;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    for (i = 1; i < argc; i++) {
        if (strcmp(argv[i], "main") == 0) {
            doblock = 1;
        } else if (argv[i][0] == 'a') {
            alarmthread = atoi(&argv[i][1]);
            if (alarmthread > NTHR || alarmthread < 1)
                err_quit("thread ID must be between 1 and %d\n", NTHR);
        } else {
            id = atoi(argv[i]);
            if (id > NTHR || id < 1)
                err_quit("thread ID must be between 1 and %d\n", NTHR);
            block[id-1] = 1;
        }
    }
    if (sigaction(SIGALRM, &act, NULL) < 0)
        err_sys("can't install signal handler");
    for (i = 0; i < NTHR; i++) {
        err = pthread_create(&tid[i], NULL, thr_fn, (void *)(i+1));
        if (err != 0)
            err_exit(err, "can't create thread");
    }
    if (doblock != 0) {
        printf("main thread blocking SIGALRM...\n");
        if (pthread_sigmask(SIG_BLOCK, &mask, NULL))
            err_sys("main thread: can't block SIGALRM");
    }
    if (alarmthread == 0) {
        printf("main thread: setting alarm...\n");
        set_timer();
    }
    for (;;)
        pause();
    exit(0);
}