Friday, December 25, 2009

Brief on Signal

Review SIGNAL

Signal can be used to control process. SIGKILL, SIGSTOP, SIGINT are well known types of signal. No process can mask SIGKILL and SIGSTOP.

In a program, you can register signal handler function so that when a specific signal is sent to a process it can invoke registered signal handler function.

And also you can block in a certain part of the code to wait signal.

In multi-thread program, we can set signal mask so that only specified thread can handle the signal.

* How to register signal handler.

Though we've used the function signal() before, it's ANSI compatible one but not a standard. We can use sigaction() in the standard.

sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

If handler is registered with the function above, handler will be called when the signal occurs.

We use struct sigaction structure to register handler, to set mask, flags.

struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *); // kind of advanced
    // used with sigfillset(), sigemptyset(), sigaddset(), sigdelset()
    sigset_t sa_mask;  
    int sa_flags; // option flags like
    /*
        SA_NOCLDSTOP, // child의 중지시 SIGCHLD 안받음
        SA_ONESHOT/SA_RESETHAND : 일회용 핸들러
        SA_NOMASK, SA_NODEFFER : 같은 시그널 발생을 block하지 않음
        SA_SIGINFO : 핸들러로 sa_sigaction을 사용
    */
    void (*sa_restorer)(void);
};


Sample code

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

void usr_handler(int signum);

int main()
{
    struct sigaction sa_usr1;
    struct sigaction sa_usr2;

    memset(&sa_usr1, 0, sizeof(struct sigaction));
    sa_usr1.sa_handler = usr_handler;
    sa_usr1.sa_flags = SA_NODEFER;
    sigfillset(&sa_usr1.sa_mask);

    memset(&sa_usr2, 0, sizeof(struct sigaction));
    sa_usr2.sa_handler = usr_handler;
    sa_usr2.sa_flags = SA_NODEFER;
    sigemptyset(&sa_usr2.sa_mask);

    sigaction(SIGUSR1, &sa_usr1, NULL);
    sigaction(SIGUSR2, &sa_usr2, NULL);

    printf("SIGUSR1, SIGUSR2 Registered!\n");

    for (;;)
    {
        pause();
    }

    return EXIT_SUCCESS;
}

void usr_handler(int signum)
{
    int i;
    for (i = 0; i < 5; i++)
    {
        printf("[%d]Caught signal. will sleep 1 sec : SIGUSR%d\n"
            ,i , (signum == SIGUSR1) ? 1 : 2);
        sleep(1);
    }
}


* How to block signal in a process

sigprocmask() offers signal blocking.

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

If oldset is not NULL, current signal mask will be copied into oldset.

* How to handle signal in multi-thread program.

As a default, if program receive signal, all of its thread receive the signal and it will make,. a chaos and program will halt. Generally, people set signal mask so that signal can be handled by specific thread.

Like sigprocmask, threre is pthread_sigmask(). If it's called in main(), all thread created after will have that signal mask. It it's called in a certain thread, only that thread will have that mask. In this way, we can specify which thread will receive signal.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>

#define THREADNUM 5
#define LIFETIME 10

struct s_thread_ctx
{
    pthread_t tid;
    int index;
}ctx[THREADNUM];

void sig_handler(int);
void *worker(void *);

int main()
{
    void *res;
    int i;
    sigset_t sigmask, sigmask_old;

    sigfillset(&sigmask);

    printf("[MAIN] Masking all signals\n");
    pthread_sigmask(SIG_SETMASK, &sigmask, &sigmask_old);

    for (i = 0; i < THREADNUM; i++)
    {
        ctx[i].index = i;
        pthread_create(&ctx[i].tid, NULL, worker, (void *)&ctx[i]);
        printf("[MAIN] Create %dth thread\n", i);
    }

    printf("[MAIN] Joining threads\n");
    for (i = 0; i < THREADNUM; i++)
    {
        pthread_join(ctx[i].tid, &res);
    }
    return 0;
}

void *worker(void *arg)
{
    int i;
    sigset_t sigmask;
    struct s_thread_ctx *ctx;
    struct sigaction sa;

    ctx = (struct s_thread_ctx *)arg;

    sigfillset(&sigmask);
    sigdelset(&sigmask, SIGINT);

    sa.sa_handler = sig_handler;
    sigfillset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);
    
#ifdef HANDLER_THREAD
    /* only last thread will catch SIGINT */
    if (ctx->index == (THREADNUM - 1))
    {
        pthread_sigmask(SIG_SETMASK, &sigmask, NULL);
    }
#endif

    /* Just waiting for LIFETIME seconds */
    for (i = 0; i < LIFETIME; i++)
        sleep(1);

    return arg;
}

void sig_handler(int signum)
{
    if (signum == SIGINT)
    {
        printf("[HANDLER] I've got SIGINT\n");
    }
    return;
}



* Handling signal and waiting signal.

If handler is registered, it would be called when specified signal is sent. And we can also wait until the signal arrives.

int sigsuspend(const sigset_t *mask);

Function sigsuspend will block the program until specified signals in mask arrive.

#include <signal.h>
#include <stdio.h>
#include <string.h>

void handler(int signal)
{
    printf("[Handler] Signal handled\n");
    return;
}

int main()
{
    int sig;
    sigset_t set;
    struct sigaction sa;

    memset(&sa, 0, sizeof(struct sigaction));
    sa.sa_handler = handler;
    sigemptyset(&sa.sa_mask);
    sigaddset(&sa.sa_mask, SIGUSR1);

    sigaction(SIGUSR1, &sa, NULL);

    sigfillset(&set);
    sigdelset(&set, SIGUSR1);

    sig = sigsuspend(&set);

    printf("SIGUSR1 arrived\n");

    return 0;
}




* Signal and process

If a signal is sent to a certain process, it's also sent to all of its child as long as they have same process group id. That means, if a child become a process group leader, signal will not be propagated to it any more.

cf. session = process groups and processes, process group = processes, if pid == pgid, it's process group leader.

related functions : setpid, setpgid, setpgrp, getsid, setsid

> kill -SIGUSR1 -1234 # to send SIGUSR1 signal to all processes which have pgid as 1234

No comments:

Post a Comment