David
David

Reputation: 11

Can a blocked signal wake up a process from a semaphore waiting queue?

i have this program that forks itself a lot of times (resetting its proccess block via execve every time) and another program that send signals to it so it can fork again. In the handler I blocked every signal (via sigprocmask) so that i dont receive a double signal but somehow it wakes up from the semaphore even if the signals are blocked. (the program that sends the signals doest send KILL and the other signal that cannot be blocked)

So a blocked signal can wake up a process from a waiting state? (I used semop for the semaphore wait)

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

sigset_t all_signals;

void sigusr1_handler(int signo) {
        printf("SIGUSER1 received \n");
        sigfillset(&all_signals);
        sigprocmask(SIG_SETMASK, &all_signals, NULL);
}

int main() {
    struct sigaction sa;
    bzero(&sa, sizeof(sa));
    sa.sa_handler = sigusr1_handler;
    sa.sa_flags = 0;
    sigaction(SIGUSR1, &sa, NULL);
    raise(SIGUSR1);
    raise(SIGUSR1);
    

   return 0;
}

Upvotes: 1

Views: 130

Answers (1)

Craig Estey
Craig Estey

Reputation: 33601

A few issues ...

  1. sa_mask only blocks signals while the signal handler is running.
  2. It does this by ORing in sa_mask to the current mask before calling the handler.
  3. After the handler exits, the original mask is restored.
  4. So, although sigprocmask is valid/safe inside a signal handler, none of the changes will be preserved when the handler exits.
  5. Calling printf from a signal handler is unsafe (because it uses malloc/free). So, to do a safe printf, one can do sprintf followed by write

Here is some modified code:

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

sigset_t all_signals;

#define safe_printf(_fmt...) \
    do { \
        char buf[100]; \
        size_t len = sprintf(buf,_fmt); \
        write(1,buf,len); \
    } while (0)

#define SIGSHOW(_signo) \
    { .sig_name = #_signo, .sig_no = _signo },

#define SIGALL(_cmd) \
    _cmd(SIGHUP) \
    _cmd(SIGINT) \
    _cmd(SIGQUIT) \
    _cmd(SIGILL) \
    _cmd(SIGTRAP) \
    _cmd(SIGABRT) \
    _cmd(SIGIOT) \
    _cmd(SIGBUS) \
    _cmd(SIGFPE) \
    _cmd(SIGKILL) \
    _cmd(SIGUSR1) \
    _cmd(SIGSEGV) \
    _cmd(SIGUSR2) \
    _cmd(SIGPIPE) \
    _cmd(SIGALRM) \
    _cmd(SIGTERM) \
    _cmd(SIGSTKFLT) \
    _cmd(SIGCHLD) \
    _cmd(SIGCONT) \
    _cmd(SIGSTOP) \
    _cmd(SIGTSTP) \
    _cmd(SIGTTIN) \
    _cmd(SIGTTOU) \
    _cmd(SIGURG) \
    _cmd(SIGXCPU) \
    _cmd(SIGXFSZ) \
    _cmd(SIGVTALRM) \
    _cmd(SIGPROF) \
    _cmd(SIGWINCH) \
    _cmd(SIGIO) \
    _cmd(SIGPWR) \
    _cmd(SIGSYS)

struct sigshow {
    const char *sig_name;
    int sig_no;
};

struct sigshow siglist[] = {
    SIGALL(SIGSHOW)
    { .sig_name = NULL },
};

void
showmask(const sigset_t *set,const char *who)
{
    char buf[1000];
    char *rhs = buf;
    char *lhs = buf;
    sigset_t mask;

    if (set == NULL) {
        set = &mask;
        sigprocmask(SIG_SETMASK,NULL,&mask);
    }

    rhs += sprintf(rhs,"showmask (from %s):\n",who);

    for (struct sigshow *show = siglist;  show->sig_name != NULL;  ++show) {
        if (sigismember(set,show->sig_no))
            rhs += sprintf(rhs," %s",show->sig_name);
        if ((rhs - lhs) >= 66) {
            rhs += sprintf(rhs,"\n");
            lhs = rhs;
        }
    }

    rhs += sprintf(rhs,"\n");

    write(1,buf,rhs - buf);
}

void
sigusr1_handler(int signo)
{
    //safe_printf("SIGUSER1 received \n");
    showmask(NULL,"handler/ENTER");
    sigfillset(&all_signals);
    sigprocmask(SIG_SETMASK, &all_signals, NULL);
    showmask(NULL,"handler/EXIT");
}

int
main()
{
    struct sigaction sa;

    showmask(NULL,"main/ENTER");

    bzero(&sa, sizeof(sa));
    sa.sa_handler = sigusr1_handler;
    sa.sa_flags = 0;
    sigaddset(&sa.sa_mask,SIGUSR1);

    showmask(&sa.sa_mask,"main/sa_mask");
    sigaction(SIGUSR1, &sa, NULL);

    showmask(NULL,"main/preraise1");
    raise(SIGUSR1);
    showmask(NULL,"main/preraise2");
    raise(SIGUSR1);
    showmask(NULL,"main/postraise2");

    return 0;
}

Here is the program output:

showmask (from main/ENTER):

showmask (from main/sa_mask):
 SIGUSR1
showmask (from main/preraise1):

showmask (from handler/ENTER):
 SIGUSR1
showmask (from handler/EXIT):
 SIGHUP SIGINT SIGQUIT SIGILL SIGTRAP
 SIGABRT SIGIOT SIGBUS SIGFPE SIGUSR1 SIGSEGV SIGUSR2 SIGPIPE SIGALRM
 SIGTERM SIGSTKFLT SIGCHLD SIGCONT SIGTSTP SIGTTIN SIGTTOU SIGURG SIGXCPU
 SIGXFSZ SIGVTALRM SIGPROF SIGWINCH SIGIO SIGPWR SIGSYS
showmask (from main/preraise2):

showmask (from handler/ENTER):
 SIGUSR1
showmask (from handler/EXIT):
 SIGHUP SIGINT SIGQUIT SIGILL SIGTRAP
 SIGABRT SIGIOT SIGBUS SIGFPE SIGUSR1 SIGSEGV SIGUSR2 SIGPIPE SIGALRM
 SIGTERM SIGSTKFLT SIGCHLD SIGCONT SIGTSTP SIGTTIN SIGTTOU SIGURG SIGXCPU
 SIGXFSZ SIGVTALRM SIGPROF SIGWINCH SIGIO SIGPWR SIGSYS
showmask (from main/postraise2):

UPDATE:

Oh I understand thanks for explaining. Ik about the printf being unsafe but it was only for the test.

Although, for this limited test case, it might be safe (because the base level is not doing any printf or malloc), I wanted to be sure. Especially, since I added some base level printf.

So are there any effective ways of blocking all signals in a handler for the whole process or it can only be done in the main? – David

I was thinking that doing getcontext, modifying uc_sigmask, and calling setcontext might work. So, I tried it. I just got [effectively] an infinite loop where the handler is never exited.

More/better trickery with this might be possible. But... I don't think it's worth the effort as it would be a bit fragile if it could be made to work at all.

Without seeing all of your code, it's difficult to give a definitive answer. That is, what the sender is doing, what the receiver is doing when not processing a signal, etc.

But, a better proposition is to rearchitect the "signaling" mechanism.

That is, SIGUSR1 is not queued. So, a program that does rapid signalling could race against the receiver and some signals could be lost. Better to use a realtime signal because they are queued

Also, signals are asynchronous to the recipient. They are designed for exceptional conditions and not to send "messages" between processes.

There are race conditions galore in the recipient between receiving the signal (in the handler) and masking so the child process you do fork/execve for doesn't get the signals (even with RT signals).

A better way would be to use SysV IPC to send/receive messsages (i.e. msgsnd/msgrcv). With this mechanism, the recipient is not racing against itself.

Also, we can eliminate the semaphore altogether.

I've used SysV IPC for many similar purposes in commercial products for realtime systems with great success.

For a small example of this, loosely derived from my production code, see my answer: In C, is storing data in local variables in threads akin to creating a local copy? AKA Does this Threadpool synchronization make sense?

Upvotes: 0

Related Questions