11

I am reading APUE and the Interrupted System Calls chapter confuses me.

I would like to write down my understanding based on the book, please correct me.

  1. A characteristic of earlier UNIX systems was that if a process caught a signal while the process was blocked in a ‘‘slow’’ system call, the system call was interrupted. The system call returned an error and errno was set to EINTR. This was done under the assumption that since a signal occurred and the process caught it, there is a good chance that something has happened that should wake up the blocked system call.

    So it's saying that the earlier UNIX systems has a feature: if my program uses a system call, it would be interrupted/stopped, if at any time the program catches a signal. (Does default handler also count as a catch?)

    For example, if I have a read system call, which reads 10GB data, when it's reading, I send any one of signals(e.g.kill -SIGUSR1 pid), then read would fail and return.


  1. To prevent applications from having to handle interrupted system calls, 4.2BSD introduced the automatic restarting of certain interrupted system calls. The system calls that were automatically restarted are ioctl, read, readv, write, writev, wait, and waitpid. As we’ve mentioned, the first five of these functions are interrupted by a signal only if they are operating on a slow device; wait and waitpid are always interrupted when a signal is caught. Since this caused a problem for some applications that didn’t want the operation restarted if it was interrupted, 4.3BSD allowed the process to disable this feature on a per-signal basis.

So before automatic restarting was introduced, I had to handle interrupted system call on my own. I need write code like:

The problem with interrupted system calls is that we now have to handle the error return explicitly. The typical code sequence (assuming a read operation and assuming that we want to restart the read even if it’s interrupted) would be:

again:
    if ((n = read(fd, buf, BUFFSIZE)) < 0) {
        if (errno == EINTR)
        goto again; /* just an interrupted system call */
        /* handle other errors */
}

But nowadays I don't have to write this kind of code, beacause of the automatic restarting mechanism.


So if I my understanding are all correct, what/why should I care about interrupted system call now..? It seems the system/OS handles it automatically.

1

2 Answers 2

9

Interruption of a system call by a signal handler occurs only in the case of various blocking system calls, and happens when the system call is interrupted by a signal handler that was explicitly established by the programmer.

Furthermore, in the case where a blocking system call is interrupted by a signal handler, automatic system call restarting is an optional feature. You elect to automatically restart system calls by specifying the SA_RESTART flag when establishing the signal handler. As stated in (for example) the Linux signal(7) manual page:

   If  a  signal  handler  is  invoked while a system call or library
   function call is blocked, then either:

   * the call is automatically restarted  after  the  signal  handler
     returns; or

   * the call fails with the error EINTR.

   Which  of  these two behaviors occurs depends on the interface and
   whether or not  the  signal  handler  was  established  using  the
   SA_RESTART  flag (see sigaction(2)). 

As hinted by the last sentence quoted above, even when you elect to use this feature, it does not work for all system calls, and the set of system calls for which it does work varies across UNIX implementations. The Linux signal(7) manual page notes a number of system calls that are automatically restarted when using the SA_RESTART flag, but also goes on to note various system calls that are never restarted, even if you specify that flag when establishing a handler, including:

   * "Input" socket interfaces, when a timeout (SO_RCVTIMEO) has been
     set  on  the  socket  using  setsockopt(2):  accept(2), recv(2),
     recvfrom(2), recvmmsg(2) (also with  a  non-NULL  timeout  argu‐
     ment), and recvmsg(2).

   * "Output"  socket  interfaces,  when  a timeout (SO_RCVTIMEO) has
     been set on the socket using setsockopt(2): connect(2), send(2),
     sendto(2), and sendmsg(2).

   * File   descriptor   multiplexing   interfaces:    epoll_wait(2),
     epoll_pwait(2), poll(2), ppoll(2), select(2), and pselect(2).

   * System  V  IPC  interfaces:  msgrcv(2), msgsnd(2), semop(2), and
     semtimedop(2).

For these system calls, manual restarting using a loop of the form described in APUE is essential, something like:

while ((ret = some_syscall(...)) == -1 && errno == EINTR)
    continue;
if (ret == -1)
    /* Handle error */ ;
3

[I haven't read that APUE thing but the stuff you're quoting doesn't look too good]

if my program uses a system call, it would be interrupted/stopped, if at any time the program catches a signal.

Not any system call. Only some system calls are interruptible.

(Does default handler also count as a catch?)

No.

For example, if I have a read system call, which reads 10GB data, when it's reading, I send any one of signals(e.g.kill -SIGUSR1 pid), then read would fail and return.

Your 10GB read() will only return with EINTR if it was interrupted before being able to read even a single byte; otherwise it will return the amount of data it had already read (short read = success, errno not relevant).

[this was not explained in the linked dupe]

So if I my understanding are all correct, what/why should I care about interrupted system call now..? It seems the system/OS handles it automatically.

Because you may want to do something upon receiving a signal, and you cannot do much from a signal handler; anything using malloc() or stdio (even printf()) is out of the question. So you have to handle the interrupts in the main loop of the program, and to be able to do that, you should break somehow from a blocking read() (a read() can block even after a poll() has returned a fd as ready for reading).

[this was also explained in the linked dupe]

2
  • 2
    Reading a regular file even from a very fast SSD does block if the data to be read can't be found in cache in RAM. The behaviour does not depend on whether the file is regular or not, but it is all up to the device. If the device can't respond immediately and the process needs to wait for DMA and interrupts to complete, then the process is rescheduled and the system call blocks. The need for read to return EINTR if no data is yet available stems from the special semantics of a zero return, which normally means end of file. Mar 29, 2019 at 9:25
  • OK, I removed the "regular file" stuff, since it may be incorrect. But then, where does the need for write() to return -1/EINTR stem from ;-) ?
    – user313992
    Mar 29, 2019 at 10:23

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .