Tuesday, October 14, 2014

Unix Prog: sigsuspend function(1)

1. Signal Suspending Problem

If we want to mask off a signal, do some critical jobs, and then re-enable the signal, waiting for it to occur. We may write code like this:

//Block the signal
sigprocmask(SIG_BLOCK, &newmask, &oldmask);

/* Do some critical jobs */

//Unblock the signal
sigprocmask(SIG_SETMASK, &oldmask, NULL);

// wait for the signal to occur
pause();

There is a very small time window between sigprocmask and pause, if the signal occurs at this period, our program will never wake up.

We need one atomic operation to do both things: unblock the signal and pause.

2. sigsuspend system definition
 ubuntu@ip-172-31-23-227:~$ less /usr/include/signal.h  
 ......  
 /* Change the set of blocked signals to SET,  
   wait until a signal arrives, and restore the set of blocked signals.  
   
   This function is a cancellation point and therefore not marked with  
   __THROW. */  
 extern int sigsuspend (const sigset_t *__set) __nonnull ((1));  
 ......  

Note: sigsuspend only return -1 when stopping suspending when signal occurs, if it is paused, it doesn't return. So sigsuspend is having a problem if it does NOT return -1.

3. Example:
suspend.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
 #include<signal.h>  
 #include<errno.h>  
   
 void pr_mask(const char* str)  
 {  
  sigset_t sigset;  
  int errno_save;  
   
  errno_save = errno;  
   
  if(sigprocmask(0, NULL, &sigset) < 0) {  
   printf("sigprocmask error!\n");  
   exit(1);  
  }  
   
  printf("%s", str);  
   
  if(sigismember(&sigset, SIGINT)) printf("SIGINT ");  
  if(sigismember(&sigset, SIGQUIT)) printf("SIGQUIT ");  
  if(sigismember(&sigset, SIGUSR1)) printf("SIGUSR1 ");  
  if(sigismember(&sigset, SIGALRM)) printf("SIGALRM ");  
   
  printf("\n");  
   
  errno = errno_save;  
 }  
   
 void sig_int(int signo)  
 {  
  pr_mask("\nin sig_int: ");  
 }  
   
 int main(int argc, char* argv[])  
 {  
  sigset_t newmask, oldmask, waitmask;  
   
  pr_mask("program start: ");  
   
  if(signal(SIGINT, sig_int) == SIG_ERR)  
   printf("signal error!\n");  
  sigemptyset(&waitmask);  
  sigaddset(&waitmask, SIGUSR1);  
  sigemptyset(&newmask);  
  sigaddset(&newmask, SIGALRM);  
   
  // Block the SIGINT signal  
  if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {  
   printf("sigprocmask error!\n");  
   exit(1);  
  }  
   
  // It should output the SIGUSR1 signal only  
  pr_mask("in critical region: ");  
   
  // sigsuspend should always return -1, if not -1, it has some error  
  // During suspending period, it block one more signal SIGUSR1  
  if(sigsuspend(&waitmask) != -1) {  
   printf("sigsuspend error!\n");  
   exit(2);  
  }  
   
  pr_mask("after return from sigsuspend: ");  
   
  // Unblock all signals  
  if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {  
   printf("sigprocmask error!\n");  
   exit(1);  
  }  
   
  pr_mask("program exit: ");  
  exit(0);  
 }  

shell:
At the beginning of the program, we setup the signal mask to be SIGALRM.
After calling sigsuspend, the current mask becomes: SIGUSR1(not having SIGALRM)
Then we type the interrupt character, and the program enters sig_int handler, where SIG_INT is masked off in addition to the SIGUSR1.
After leaving sig_int handler, SIGINT is enabled. Then sigsuspend returned, the masked signal is restored to original status: only SIGALRM is masked.
Before program exited, we unblock all signals.
 ubuntu@ip-172-31-23-227:~$ ./suspend.out  
 program start:  
 in critical region: SIGALRM  
 ^C  
 in sig_int: SIGINT SIGUSR1  
 after return from sigsuspend: SIGALRM  
 program exit:  

No comments:

Post a Comment