2

I am learning concepts of signals in the C language and met a problem when building a program for practices. In the codes below, I am trying to reset SIGINT each time after the user press "ctrl-c" and to record how many times the user press "ctrl-c".

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

void handler(int signo);
jmp_buf buf;
int int_counting = 1;

void handler(int signo)
{
    signal(SIGINT, SIG_DFL);
    int_counting++;
    // just tried to add another "signal(SIGINT, handler)" here
    // but saw the same result
    longjmp(buf, 1);
}

int main()
{
    if ((signal(SIGINT, handler) == SIG_ERR))
    {
        printf("Fail to catch the signal\n");
    }
    if (!setjmp(buf))
    {
        printf("Waiting for any signals ... \n");
    }
    else
    {   
        if (!setjmp(buf)){} // to reset "setjmp" to zero
        printf("Pressed 'ctrl-c' for %d times\n", int_counting);
        printf("Waiting for another signal\n");
        signal(SIGINT, handler);
    }
    while (int_counting <= 5)
    {
        sleep(1);
        printf("Processing ...\n");
    }
}

However, after the first signal no other signals can be sent to handler and the output looks like: enter image description here

Could you anyone explains the reason?

Below are examples where it seems like the signal will not be masked.

// Examples for SIGALRM

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

int counting = 0;

void handler(int signo)
{
    printf("%d\n", counting);
    while (counting < 5)
    {
        signal(SIGALRM, handler);
        printf("%d\n", beeps);
        counting++
        alarm(1);
    }
}

void main(void)
{
    if (signal(SIGALRM, handler) == SIG_ERR)
    {
        printf("cannot catch SIGALRM\n");
    }
    alarm(1);
    while (counting < 5)
    {
        pause();
    }
    return;
}
// Example for SIGQUIT

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

jump_buf buf;

void handler(int signo)
{
    signal(SIQQUIT, handler);
    longjmp(buf, 1);
}

int main()
{
    signal(SIQQUIT, handler);
    if (!setjmp(buf))
    {
        printf("begin ...\n");
    }
    else
    {
        print("restart ...\n");
    }
    while (1)
    {
        sleep(1);
        printf("waiting for sinals ...\n");
    }
}

Although my original question is answered but if any further explanation about why those signals will not be masked (or please tell me that is how they work in C), it would be greatly helpful.

  • IMHO, in my humble opinion, you should first go through this https://www.geeksforgeeks.org/signals-c-language/ If your objective is just to test signals then choosing a hello world is better. Focusing on the objective of the program reduces unnecessary complexities in code. – CallMe RK Mar 22 '21 at 18:10
  • By the way, trying to catch a signal inside a signal handler would not work.. signal would be pending by then.. it's like waiting for an interrupt inside an interrupt handler.. That could as well be your problem. – CallMe RK Mar 22 '21 at 18:12
  • Thanks for your advice. I viewed your attached link already but did not find anything helpful ... Here I am trying to jump between the ```handler``` and the ```main``` but I do not understand why the ```signal(SIGINT, handler)``` in the ```else``` statement in ```main``` does not work – Sanae Kochiya Mar 22 '21 at 18:19
  • @breiters Because I cannot stack signals, I do not know how to run ```main => handler => main => handler``` iteratively like how I did by ```alarm```. Or maybe I can send out alarm in the ```SIGINT handler``` and work on the main part in ```SIGALRM handler``` – Sanae Kochiya Mar 22 '21 at 18:45
  • @SanaeKochiya You could also simply return from the signal handler **without** a `longjmp` if you do not require to set the signal handler to `SIG_DFL` during signal handling. – breiters Mar 22 '21 at 19:21

2 Answers2

1

You need to save the signal mask and therefore use siglongjmp() and sigsetjmp().

This works as expected:

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

void handler(int signo);
sigjmp_buf buf;
int int_counting = 0;

void handler(int signo)
{
    signal(SIGINT, SIG_DFL);
    int_counting++;
    // just tried to add another "signal(SIGINT, handler)" here
    // but saw the same result
    siglongjmp(buf, 1); // 1: "fake" return value
}
int main()
{
    if ((signal(SIGINT, handler) == SIG_ERR))
    {
        printf("Fail to catch the signal\n");
    }
    if (!sigsetjmp(buf, 1)) // 1 (or any non-zero value): save sigmask
    {
        printf("Waiting for any signals ... \n");
    }
    else // this code is executed when the "fake" return value of sigsetjmp is non-zero
    {
        printf("Pressed 'ctrl-c' for %d times\n", int_counting);
        printf("Waiting for another signal\n");
        signal(SIGINT, handler);
    }
    while (int_counting <= 5)
    {
        sleep(1);
        printf("Processing ...\n");
    }
}

It is described in the man-page e.g. man setjmp:

sigsetjmp() and siglongjmp() also perform nonlocal gotos, but provide predictable handling of the process signal mask

breiters
  • 116
  • 6
  • Thank you for your answers! I knew nothing about the library `````` but this sounds like the so called "goto" (namely ```setjmp + longjmp```) combo can only works for once between two different contex. – Sanae Kochiya Mar 22 '21 at 19:39
1

SIGINT is masked (blocked) during execution of your signal handler, and remains masked when you longjmp out of it. That's why you don't see subsequent SIGINTs — the signal mask prevents their delivery.

The fix is three-fold.

First, use sigsetjmp(buf, 1) to save the calling mask and siglongjmp make the jump. That will restore the signal mask to its expected value at that point in execution.

Second, use sigaction rather than signal. sigaction will force you to explicitly choose behavior like masking out signals during handler execution. signal, on the other hand, does not mandate consistent behavior across platforms.

Third, don't use (sig)longjmp at all. You got into this trouble because you are issuing a non-local goto from within asynchronously executed user code (your handler). It's very easy to make mistakes when reasoning about that kind of code.

pilcrow
  • 56,591
  • 13
  • 94
  • 135
  • Thank you for your detailed explanation. This is the first time when I heard about the ```SIGINT``` will be masked within the handler. May I ask is ```SIGINT``` the only signal that will be masked? For example, it seems like ```SIGALRM``` will not be masked (an example can be found in the code inside this post) and ```SIGQUIT``` either. – Sanae Kochiya Mar 22 '21 at 19:42
  • @SanaeKochiya, if you use `signal`, the answer varies from platform to platform. [Use `sigaction` instead](https://stackoverflow.com/questions/231912/what-is-the-difference-between-sigaction-and-signal/232711#232711) and you will _know_ the answers to your detailed questions. – pilcrow Mar 22 '21 at 19:45