Saturday, October 11, 2014

Unix Prog: alarm and pause functions(1)

1. System Definitions:
 ubuntu@ip-172-31-23-227:~$ less /usr/include/unistd.h  
 ......  
 /* Schedule an alarm. In SECONDS seconds, the process will get a SIGALRM.  
   If SECONDS is zero, any currently scheduled alarm will be cancelled.  
   The function returns the number of seconds remaining until the last  
   alarm scheduled would have signaled, or zero if there wasn't one.  
   There is no return value to indicate an error, but you can set `errno'  
   to 0 and check its value after calling `alarm', and this might tell you.  
   The signal may come late due to processor scheduling. */  
 extern unsigned int alarm (unsigned int __seconds) __THROW;  
 ......  
 /* Suspend the process until a signal arrives.  
   This always returns -1 and sets `errno' to EINTR.  
   
   This function is a cancellation point and therefore not marked with  
   __THROW. */  
 extern int pause (void);  
 ......  

2. Example:
sleep.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
 #include<signal.h>  
   
 static void sig_alrm(int signo)  
 {  
  /* do nothing here, just return */  
 }  
   
 int main(int argc, char* argv[])  
 {  
  if(signal(SIGALRM, sig_alrm) == SIG_ERR) {  
   printf("signal error!\n");  
   exit(1);  
  }  
   
  alarm(2);  
  pause();  
   
  //alarm(0) will return the remaining seconds of last alarm call  
  //Since Unix system may have some processor scheduling delays.  
  return(alarm(0));  
 }  

The program firstly setup the SIGALRM handler, and then start alarm system call, then pause while waiting for the alarm system call expires, and after 2 seconds, the process wake up and return. This is a simulation of "sleep" system call.

It has one potential problems:
alarm(2) and pause() have the race condition in this case. It is possible that, after alarm goes off, the scheduler delays for more than 2 seconds and SIG_ALRM occurs before pause() starts.

Following file solve this problem:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
 #include<signal.h>  
 #include<setjmp.h>  
   
 static jmp_buf env_alrm;  
   
 static void sig_alrm(int signo)  
 {  
  longjmp(env_alrm, 1);  
 }  
   
 int main(int argc, char* argv[])  
 {  
  if(signal(SIGALRM, sig_alrm) == SIG_ERR) {  
   printf("signal error!\n");  
   exit(1);  
  }  
   
  if(setjmp(env_alrm) == 0) {  
   alarm(2);  
   pause();  
  }  
   
  return(alarm(0));  
 }  

Even if the SIG_ALRM occurs before pause() system call, it will jump to the line: setjmp with return value 1, to continue running.

But it still has a subtle problem, it uses longjmp to jump to another place. If the process is executing inside the signal handler, then that handler is aborted.
Following example illustrates this issue:

sleep.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
 #include<signal.h>  
 #include<setjmp.h>  
   
 static jmp_buf env_alrm;  
   
 static void sig_alrm(int signo)  
 {  
  longjmp(env_alrm, 1);  
 }  
   
 int sleep2(int nsec)  
 {  
  if(signal(SIGALRM, sig_alrm) == SIG_ERR) {  
   printf("signal error!\n");  
   exit(1);  
  }  
   
  if(setjmp(env_alrm) == 0) {  
   alarm(nsec);  
   pause();  
  }  
   
  return(alarm(0));  
 }  
   
 void sig_int(int signo)  
 {  
  int i,j;  
  volatile int k;  
   
  printf("\nsig_int starting\n");  
  for(i = 0; i < 300000; i++)  
   for(j = 0; j < 4000; j++)  
    k+= i*j;  
  printf("sig_int finished\n");  
 }  
   
 int main(int argc, char* argv[])  
 {  
  unsigned int unslept;  
   
  if(signal(SIGINT, sig_int) == SIG_ERR) {  
   printf("signal(SIGINT) error!\n");  
   exit(1);  
  }  
  unslept = sleep2(3);  
  printf("sleep2 returned %u\n", unslept);  
  exit(0);  
 }  

shell:
The program firstly setup the sig_int handler for signal SIGINT, and then it start sleeping, for 3 seconds.
During 3 seconds, user type Ctrl C to send the SIGINT signal to the process. sig_int handler is triggered to enter one very long loop, during this period, alarm call in sleep2 expires, which triggered SIGALRM signal, whose handler use sigjmp to abort the sig_int handler. So we don't see the output "sig_int finished".
 ubuntu@ip-172-31-23-227:~$ ./sleep.out  
 ^C  
 sig_int starting  
 sleep2 returned 0  

No comments:

Post a Comment