Search code examples
calarm

Schedule alarm using c


Our task is to develop an application that creates a child process. The parent process then waits 3 seconds (we are not allowed to use sleep in the parent process) and sends a SIGUSR2 signal followed by a SIGUSR1 signal. After that, the parent sends the SIGUSR1 signal periodically (3 seconds) to the child until it terminates. Upon child termination it prints Parent done and exits. We should implement this behaviour using alarm() and pause().

The child process should block the signal SIGUSR2 for 13 seconds. Upon receiving SIGUSR1 it should print Received SIGUSR1. Upon receiving SIGUSR2 it prints Child done and exits. My approach to implement this is the following:

Please note: the SIGUSR2 signal is only sent once, until now I did not implement that and it is sent each time along with the SIGUSR1 signal.

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

static pid_t chld;

void init_signal(struct sigaction* action, void (*handler)(int));
void init_sigset(sigset_t* set, int signum);
void bind_signal(struct sigaction* action, int n, ...);
void par_signal(int signum);
void chld_signal(int signum);

int main(){
    chld = fork();
    struct sigaction action;

    if (chld == -1){
        fprintf(stderr, "Failed to create child process.\n");
        return EXIT_FAILURE;
    } else if (chld == 0){
        init_signal(&action, &chld_signal);
        bind_signal(&action, 3, SIGUSR1, SIGUSR2, SIGALRM);        

        sigset_t sig;
        init_sigset(&sig, SIGUSR2);

        sigprocmask(SIG_BLOCK, &sig, NULL);
        alarm(13);
        pause();
    }
    init_signal(&action, &par_signal);
    bind_signal(&action, 2, SIGALRM, SIGCHLD);

    alarm(3);
    pause();
}

void init_signal(struct sigaction* action, void (*handler)(int)){
    action->sa_flags = 0;
    action->sa_handler = handler;

    sigemptyset(&action->sa_mask);
}

void init_sigset(sigset_t* set, int signum){
    sigemptyset(set);
    sigaddset(set, signum);
}

void bind_signal(struct sigaction* action, int n, ...){
    va_list args;
    va_start(args, n);

    for (int i = 0; i < n; i++){
        int signum = va_arg(args, int);
        sigaction(signum, action, NULL);
    }
    va_end(args);
}

void par_signal(int signum){
    if (signum == SIGALRM){
        kill(chld, SIGUSR2);
        kill(chld, SIGUSR1);
        alarm(3);
        pause();
    } else if (signum == SIGCHLD){
        printf("Parent done\n");
        exit(EXIT_SUCCESS);
    }
}

void chld_signal(int signum){
    if (signum == SIGALRM){
        sigset_t sig;
        init_sigset(&sig, SIGUSR2);

        sigprocmask(SIG_UNBLOCK, &sig, NULL);
        pause();
    } else if (signum == SIGUSR1){
        printf("Received SIGUSR1\n");
        pause();
    } else if (signum == SIGUSR2){
        printf("Child done\n");
        exit(EXIT_SUCCESS);
    }
}

It works apart from the fact, that only the first signal of the parent process is received by the child. I figure this has something to do with my usage of pause respectively alarm. Do you have any idea what I am doing wrong?

The link to the exercise tasksheet can be found here (Task 3).


Solution

  • Calling pause in the signal handler is problematic because in this situation the signal that just triggered the signal handler is blocked until you return from the signal handler. That means the process will no longer react to this signal. The signal handler can only be triggered by other signals.

    See below a system call trace with explanations. I replaced the parent's PID with @PARENT@ and the child's PID with #CHILD## for clarity.

    $ strace -f -r -e trace=signal,process ./proc
         0.000000 execve("./proc", ["./proc"], 0x7ffc146ba360 /* 74 vars */) = 0
         0.001428 arch_prctl(0x3001 /* ARCH_??? */, 0x7ffd3c958fc0) = -1 EINVAL (Invalid argument)
         0.002302 arch_prctl(ARCH_SET_FS, 0x7f264e40a540) = 0
    # clone is fork()
         0.001086 clone(strace: Process #CHILD## attached
    child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f264e40a810) = #CHILD##
    # parent establishes SIGALRM handler
    [pid @PARENT@]      0.000791 rt_sigaction(SIGALRM, {sa_handler=0x564709a5859c, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f264e25e470},  <unfinished ...>
    # child establishes SIGUSR1 handler
    [pid #CHILD##]      0.000087 rt_sigaction(SIGUSR1, {sa_handler=0x564709a58605, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f264e25e470},  <unfinished ...>
    [pid @PARENT@]      0.000132 <... rt_sigaction resumed> NULL, 8) = 0
    [pid #CHILD##]      0.000056 <... rt_sigaction resumed> NULL, 8) = 0
    # child establishes SIGUSR2 handler
    [pid #CHILD##]      0.000072 rt_sigaction(SIGUSR2, {sa_handler=0x564709a58605, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f264e25e470}, NULL, 8) = 0
    # child establishes SIGALRM handler
    [pid #CHILD##]      0.000141 rt_sigaction(SIGALRM, {sa_handler=0x564709a58605, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f264e25e470}, NULL, 8) = 0
    # parent establishes SIGCHLD handler
    [pid @PARENT@]      0.000389 rt_sigaction(SIGCHLD, {sa_handler=0x564709a5859c, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f264e25e470},  <unfinished ...>
    # child blocks SIGUSR2
    [pid #CHILD##]      0.000132 rt_sigprocmask(SIG_BLOCK, [USR2], NULL, 8) = 0
    # child waits for signal
    [pid #CHILD##]      0.000204 pause( <unfinished ...>
    [pid @PARENT@]      0.000837 <... rt_sigaction resumed> NULL, 8) = 0
    # parent waits for signal
    [pid @PARENT@]      0.000133 pause()       = ? ERESTARTNOHAND (To be restarted if no handler)
    # parent receives SIGALRM
    [pid @PARENT@]      3.000317 --- SIGALRM {si_signo=SIGALRM, si_code=SI_KERNEL} ---
    # parent sends SIGUSR2 and SIGUSR1 to child
    [pid @PARENT@]      0.000106 kill(#CHILD##, SIGUSR2) = 0
    [pid @PARENT@]      0.000116 kill(#CHILD##, SIGUSR1) = 0
    [pid #CHILD##]      0.000144 <... pause resumed> ) = ? ERESTARTNOHAND (To be restarted if no handler)
    # child receives SIGUSR1 (SIGUSR2 is blocked)
    [pid #CHILD##]      0.000166 --- SIGUSR1 {si_signo=SIGUSR1, si_code=SI_USER, si_pid=@PARENT@, si_uid=1000} ---
    # parent waits for signal again. As this "pause" was called from a signal handler triggered by SIGALRM, this signal is now blocked.
    [pid @PARENT@]      0.000227 pause( <unfinished ...>
    # child waits for signal. As this "pause" was called from a signal handler triggered by SIGUSR1, this signal is now blocked.
    [pid #CHILD##]      0.000566 pause()       = ? ERESTARTNOHAND (To be restarted if no handler)
    # child receives SIGALRM
    [pid #CHILD##]      9.997742 --- SIGALRM {si_signo=SIGALRM, si_code=SI_KERNEL} ---
    # child unblocks SIGUSR2
    [pid #CHILD##]      0.000102 rt_sigprocmask(SIG_UNBLOCK, [USR2], NULL, 8) = 0
    # child now receives SIGUSR2 which was already sent by the parent
    [pid #CHILD##]      0.000122 --- SIGUSR2 {si_signo=SIGUSR2, si_code=SI_USER, si_pid=@PARENT@, si_uid=1000} ---
    # child exits
    [pid #CHILD##]      0.000183 exit_group(0) = ?
    [pid #CHILD##]      0.000204 +++ exited with 0 +++
         0.000081 <... pause resumed> )     = ? ERESTARTNOHAND (To be restarted if no handler)
    # parent receives SIGCHLD from child
         0.000056 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=#CHILD##, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
    Parent done
    # parent exits
         0.000563 exit_group(0)             = ?
         0.000301 +++ exited with 0 +++
    

    I suggest to set flags from the signal handlers and do the decisions and waiting in the main function. Something like this

    static volatile sig_atomic_t childSigUsr1 = 0;
    static volatile sig_atomic_t childSigUsr2 = 0;
    static volatile sig_atomic_t childSigAlrm = 0;
    
    void chld_signal(int signum) {
        if (signum == SIGALRM) {
            childSigAlrm = 1;
        } else if (signum == SIGUSR1) {
            childSigUsr1 = 1;
        } else if (signum == SIGUSR2) {
            childSigUsr2 = 1;
        }
    }
    
    int main() {
        chld = fork();
        struct sigaction action;
    
        if (chld == -1) {
            fprintf(stderr, "Failed to create child process.\n");
            return EXIT_FAILURE;
        } else if (chld == 0) {
            init_signal(&action, &chld_signal);
            bind_signal(&action, 3, SIGUSR1, SIGUSR2, SIGALRM);        
    
            sigset_t sig;
            init_sigset(&sig, SIGUSR2);
    
            sigprocmask(SIG_BLOCK, &sig, NULL);
            alarm(13);
            while(1) {
                pause();
                if(childSigUsr1) {
                    childSigUsr1 = 0;
                    printf("Received SIGUSR1\n");
                }
                if(childSigUsr2) {
                    childSigUsr2 = 0;
                    printf("Child done\n");
                    exit(EXIT_SUCCESS);
                }
                if(childSigAlrm) {
                    sigset_t sig;
                    init_sigset(&sig, SIGUSR2);
    
                    sigprocmask(SIG_UNBLOCK, &sig, NULL);
                }
            }
        }
        init_signal(&action, &par_signal);
        bind_signal(&action, 2, SIGALRM, SIGCHLD);
    
        while(1) {
            alarm(3);
            pause();
            /* handle and reset signal flags similar to child */
        }
    }