Impact of Signals on Unix/Linux System Calls

来源:互联网 发布:网络协议错误怎么办 编辑:程序博客网 时间:2024/05/21 08:36

By poornaMokshaOn 8th October, 2011


Suppose a system call was blocked due to some reason (like waiting for a read of some data on terminal) and during this time a signal occurs. Do you know what happens in this scenario?

In this article we will discuss the impact of signals on system calls.

System calls are different from normal function calls as a system call takes the flow control all the way to kernel. System calls are divided into two categories :

  • Slow system calls
  • Rest of the system calls
Now, what are Slow system calls ?

Slow system calls are those that can block for ever. These calls usually wait for a condition to occur and these calls can wait for infinite time if the condition does not occur. For example :
  1. The 'pause()' function call, which by default puts the calling process into sleep until and unless a signal is caught by this process.
  2. The 'read()' function calls to pipes(for example) which can block for ever until some data is present.
  3. The 'write()' function calls to pipes(for example) which can block for ever until the pipe is ready to accept the data.
  4. Some 'ioctl()' functions.
  5. 还有某些进程间通信的函数;(参考 APUE 10.5)
The alternative definitions of the slow system calls could be :

"The system calls that can take time to execute if/when operating on a slow device (like pipe, terminal)"

If (lets say) read() function has received and transfered some data to application and is waiting to receive the rest of the data in order to give application the complete data it requested. Suppose, at this very moment a signal occurred. Now, in todays POSIX compliant systems a read() call would not fail rather it would return partial success with the partial amount of data it could read for the application. Same goes for a system function call like 'write()' which would return partial success with writing partial data that it could.

But, what does this behavior mean? Does the application need to handle the partial success cases? Does that mean some more checks in the code like :

Code:
again:  if ((n = read(fd, buffer, BUFFERSIZE)) < 0) {      if (errno == EINTR)          goto again; /* Seems like an interrupted system call, go back and try to execute once again */      /* handle other errors */               }
Thankfully, the answer to the above questions is NO. In modern day POSIX compliant systems,the interrupted system  calls are restarted automatically by default.(这段说法有问题:自动重启是BSD 4.2/4.3的做法;Linux man 3 read 说明:read 被信号终端,linux 下立刻返回,返回读取的字节数,如果什么都没读取,则返回 -1; 而APUE 10.5 说POSIX采用了部分成功的实现实现方法,应该和Linux一致。) This takes away the overhead from an application programmer to test each time whether the a call to read() or write() was a complete success or a partial success.

An Interesting Example



Now, we understand the process that a system call is interrupted when a signal occurs. Lets demonstrate it through an example.

Code:
#include<stdio.h>#include<signal.h>#include<unistd.h>int flag;void sig_handler(int signo){   flag = 1;   if (signo == SIGUSR1)       printf("received SIGUSR1\n");   else if (signo == SIGUSR2)       printf("received SIGUSR2\n");   else       printf("ERR : received signal %d\n", signo);}int main(void){    if (signal(SIGUSR1, sig_handler) == SIG_ERR)        printf("\ncan't catch SIGUSR1\n");    if (signal(SIGUSR2, sig_handler) == SIG_ERR)        printf("\ncan't catch SIGUSR2\n");    /* Lets assume a logic consumes some processor time here*/    /* now comes this while loop*/    while(flag == 0)  // Simulate a dummy wait         /* 这里有一个 race:如果信号只发送一次,而且正好在这里发生,那么flag==1了,            而pause却不会再唤醒,程序一直等待下去 */         pause(); // Make this process to sleep until a signal occurs   (A slow system call :-) )    return 0;}
  • In the above piece of code, we have created a signal handler 'sig_handler()'.
  • We have used a flag 'flag' which is initialized as '0'.
  • In the main() function, the process is made to sleep by pause() system call.
  • The process is awakened only when a signal is received(property exhibited by pause() function).
  • Whenever a signal USR1 or USR2 occurs, the signal handler 'sig_handler()' gets called.
  • In the signal handler the flag 'flag' is set to one.
  • Since the process is awakened through a signal, the while loop executes once again
  • Since 'flag' is now '1', so pause() is not called and main() returns.
So, this was a demo code that relates the practical aspect of the concept told throughout this article.

But you might be thinking, what was so interesting in that?

Now, lets suppose that whomsoever wrote this code, wrote it with a precondition that when this code will run, once a signal USR1 will occur and this program will terminate after that. Note that the programmer knows that signal would occur only once and his/her program would catch it and return gracefully.

If you have a closer look at the logic, you will observe a small flaw in the program. The flaw can be exploited in the time window between when the while loop is called for the very first time and the pause() function is yet to be executed. Suppose, during this time gap, the long awaited :-) signal occurs. Ohh, did you guess what happens in this case? I'll tell you. In this case, before calling the pause() function, signal handler is called. Now, since the signal never occurs again, so the pause() system call never returns and hence the program is blocked for ever until its killed or system is taken down.

So, we see that programming with signals also required extreme care as we do not want our process to hang due to someloophole that the programmer left.

Conclusion

To conclude, the signals hold the capability of interrupting system calls that are working with slow devices like terminals. pipes etc. The application does not need to take care of this as POSIX compliant OSs allow the rescheduling of these system calls (internally) after the signal is handled.

Stay tuned for more


同时可参考:

1. APUE 10.5 Interrupted System Call

转自:http://www.go4expert.com/forums/showthread.php?t=26869

原创粉丝点击