1

I am working on a tool handling a massive amount of signals (with different signums) with sigaction().

I need to handle the case, if a new signal is coming, while the previous was in a signal handler. Thus, I need to be able to handle the following "stack":

  1. normal flow of the process
  2. handler of signal1
  3. handler of signal2
  4. ...possibly further signal handlers...

(Afaik there is no real stack, because signal handlers run in their own context, but this is how I can illustrate my question.)

I am using the glibc2 api.

The problem is not hopeless (I can pass the signal infos to reentrant data structures in the main process to be processed later, from the main execution flow), but I need a reliable way to find out from a handler, if it is the first on the "stack" or not.

Masking signals is not okay, minimizing the loss of signals (f.e. from conflation) is a top priority.

I need reliable way. Using a global sigatomic_t as spinlock is also problematic, because I can't guarantee that a new signal isn't coming just after the beginning of signal1 handler (yet before the try to acquire the lock).

After digging a lot in manuals and glibc docs, I didn't find any reliable way to for a signal handler to find out, if he is the first or not. Is it somehow possible?

  • 1
    I haven't tried this, but maybe use sigaltstack to make all your signal handlers get called on an alternate stack. The first signal handler ought to have its stack pointer at a known spot (at the top of the alternate stack; experiment to see where it is on your arch) every time. – Mark Plotnick Sep 4 '15 at 19:55
  • Another option that may work for you if you're worried about not being able to react to signals quickly enough is to use signalfd. Rather than being hammered by lots of signal handler invocations, you can set things up so a single read can consume all your pending signals. – Mark Plotnick Sep 4 '15 at 21:51
  • zsh includes a queue for signals last I looked, maybe see how they implemented that? (under Src/signals.c) – thrig Sep 4 '15 at 21:53
  • @thrig I checked. They are using a circular queue for the "pending" (it is NOT the kernel-side pending) signal handlers, just as me. But they couldn't avoid to use a short signal blocking section. It isn't a good omen :-( – peterh says reinstate Monica Sep 4 '15 at 22:36
  • @MarkPlotnick First is good idea, now I am thinking if it is usable to avoid the signal loss. – peterh says reinstate Monica Sep 4 '15 at 22:38
2

Configure your signal handler as masking the signals, then unmask after annotating that you received this signal.

volatile_t sig_atomic_t signal_count;
void mysignalhandler(int signo) {
   sig_atomic_t depth = ++signal_count;
   pending_signals.push(signo)
   if (depth > 1) return;
   sigprocmask(<unblock all signals>)

    while (!pending_signals.empty())
       /* Process pending_signals */
}

Note there's a small race condition between the last pending_signals.empty() check and the iret. If you were going to have some check in the main code, I'd leave it as that, given your sheer number of signals, it will probably e processed soon anyway. Otherwise, you can clock again the signals at the end, and check pending_signals is still empty before returning.

  • Thank you, but I cite from my question: "Masking signals is not okay, minimizing the loss of signals (f.e. from conflation) is a top priority.". – peterh says reinstate Monica Sep 4 '15 at 22:40
  • 1
    I am minimizing that. You are only masking them for 2-3 cycles. With absolutely no masking, you won't be able to detect a signal twice. – Ángel Sep 4 '15 at 22:51
  • This is partially research project and the intention is to solve the problem without masking. A single context switch was 120 cycle as I last time measured it (I measured by calling getpid() around 1e+9 times). – peterh says reinstate Monica Sep 4 '15 at 22:59
  • I thought you were running in some kind of microcontroller. If you were running in ring 0, cli and sti would be about 1 cycle each. – Ángel Sep 4 '15 at 23:05
  • Finally I found a purely atomical solution!!! The trick of the recursive algorithms, that you need to have a very few global variables (in my example, this is 1 (!!!) ), and you have to "talk" through them exclusively through atomic operations. But anyways, thank you very much thinking together! – peterh says reinstate Monica Sep 5 '15 at 21:48
1

Here is a different approach:

You may receive many different signals, but there's a finite set of signals. Also, we don't care too much about the order they arrived, so we can simply count the number of signals we received:

long signals[SIGRTMAX];
int signal_handler(int signum) {
    signals[argc]++;

    /* Locklessly process the contents of signals */
}

gcc -O1 -masm=intel transforms it into a single instruction:

add QWORD PTR signals[0+rdi*8], 1

although it may need a LOCK prefix in case there are several cores and threads.

  • This is what I see, and see currently... – peterh says reinstate Monica Sep 4 '15 at 23:46
  • By me it makes mov rax, rsi / mov QWORD PTR [rdi], rsi, although I've made *(p++)=q. – peterh says reinstate Monica Sep 5 '15 at 0:08
  • It seems to me, __atomic_compare_exchange can do an atomic (a==b)?(a=c):(b=a). Next questions: 1) does it really work, I check the asm 2) how will I have a reentrancy-safe pendingsignal array from that. – peterh says reinstate Monica Sep 5 '15 at 0:25
0

My current best idea. A little bit of thinking is yet back, but I am sure it will work.

The trick is: the atomic_swap and a global pointer (call this SigAction* top). It serves as both of spinlock and as the pointer to the last element of the signal stack.

Thus: * if top == NULL, atomic_swap(top, myPointer) acquires the lock. * if top != NULL, atomic_swap(top, myPointer) puts myPointer into top, while myPointer will have the previous top element of the stack. * The stack is a chained list, every myPointer->next contains its next element.

SigAction* top = NULL;

void handler(SigAction* action) {
  SigAction bkp;

  restart:

  bkp = action;
  atomic_swap(&top, &action);
  if (!action) { // we acquired the lock
    run_handler(action);
    atomic_swap(&top, &action); // release the lock
    if (action!= bkp) { // if there is a new element in the stack
      action = bkp->next;
      goto restart;
    }
  } else { // the lock is not ours
    action->next = bkp;
  }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.