3 ms·
Interrupts typically do not observably interrupt currently running code. In a simple system (e.g. embedded system with no threads, all event-driven), the interr
by ambrop7 10y ago
Interrupts typically do not observably interrupt currently running code. In a simple system (e.g. embedded system with no threads, all event-driven), the interrupt handler will run, then the processor will go back to running whatever it was running before. This is not so for UNIX signals, in case you are in the middle of a system call, because the mere occurrence of the signal will change the behavior of the interrupted code.
Yes I know it's not the same because in my example single-threaded system there's no such thing as a blocking call.
Actually I don't see a good reason why signals in unix would have to cause EINTR errors in system calls. Perhaps a better solution would be to let the system call go on normally. Since the signal doesn't observably interrupt code not in a system call, why would it observably interrupt code in a system call?
In case anyone thinks, "so you can detect the signal in the main code", that is a bad answer because whatever you do you will have race conditions if the signal happens just before you enter the system call. Your only chance is to use things like ppoll() which are designed for proper signal handling, and these things could work just as well in a hypothetical unix design with no EINTR.
- gpderetta 10y agoYou can siglongjump out of a signal handler [1]. If you sigsetjump right before doing a blocking call, you can reliably detect signals. Another way to avoid the race condition in poll/select, before p{poll/select} were standardized, was to store the timeout parameter in a global variable and have the signal handler set it to zero. Finally there is the self pipe trick, which admittedly doesn't require EINTR at all. [1] This is historical unix behaviour. At one time it was specified by the SUS, but it seems that it was dropped from more recent SUS/Posix standards.
- scottlamb 10y ago> You can siglongjump out of a signal handler [1]. If you sigsetjump right before doing a blocking call, you can reliably detect signals. The problem with that approach is that if the system call has already returned by the time the signal handler runs and jumps, the system call's return gets clobbered. So if for example you're doing blocking reads/writes, you don't know how many bytes you read or wrote. If your only blocking syscall is level-driven polling this approach is fine but the self-pipe trick is easier. I wrote (10 years ago) a library to do something similar reliably. It required custom wrappers for every system call of interest so I could know by the instruction pointer in the ucontext_t whether the system call had actually run yet or not. http://www.slamb.org/projects/sigsafe/ http://www.slamb.org/projects/sigsafe/ The library's a bit stale now; it doesn't do the vsyscall thing for example.
- gpderetta 10y agoDuh! You are right, losing the results of partial read/writes is not acceptable. I guess on x86, completely unportably, you could check whether the current ip is pointing to a syscall/int instruction.
- scottlamb 10y agoYou probably want to jump if you're "just before" the syscall, too, though. So you end up with basically this: syscall wrappers: https://github.com/scottlamb/sigsafe/blob/master/src/x86_64-linux/sigsafe_syscalls.S https://github.com/scottlamb/sigsafe/blob/master/src/x86_64-... (probably could do something better with that thread local; and as I mentioned this isn't using vsyscall) signal handler: https://github.com/scottlamb/sigsafe/blob/master/src/x86_64-linux/sighandler_platform.c https://github.com/scottlamb/sigsafe/blob/master/src/x86_64-... (although it bugs me now that I iterate the whole array if the instruction pointer's not in any of the syscalls) and just to be sure, a race checker: https://github.com/scottlamb/sigsafe/blob/master/tests/race_checker/race_checker.c https://github.com/scottlamb/sigsafe/blob/master/tests/race_...
- zAy0LfpBZLC8mAC 10y ago> Another way to avoid the race condition in poll/select, before p{poll/select} were standardized, was to store the timeout parameter in a global variable and have the signal handler set it to zero. That doesn't work. At some point, the userspace code has to copy the value from the global variable to the location where the system call calling convention expects it, some time later followed by execution of the system call trap (and usually there is even the libc system call wrapper in between the two that takes care of adapting the userspace calling convention to the system call calling convention). If your signal handler gets to run in between the two, the system call timeout will remain unchanged. Or in other words: Yes, be afraid of signals, whatever clever scheme you come up with to handle them probably is wrong.
- gpderetta 10y agoIt works with select because it takes a pointer to the timespec object, which is read kernel side [1]. I misremembered poll doing the same thing but it takes a plain integer parameter. [1] not guaranteed of course, but it is historical Unix behaviour Edit: btw, i do agree with your final sentence!
- JdeBP 10y ago... and, thence, SA_RESTART.
- scottlamb 10y ago> Actually I don't see a good reason why signals in unix would have to cause EINTR errors in system calls. Perhaps a better solution would be to let the system call go on normally. Since the signal doesn't observably interrupt code not in a system call, why would it observably interrupt code in a system call? You can request this behavior via the SA_RESTART flag. I'm not sure if it applies to all syscalls (the Linux manpage suggests not). I'm also not sure if things that take a relative timeout internally subtract the elapsed time when interrupted; one could imagine that each signal snoozes the timer on nanosleep or select/poll/epoll_wait so that you oversleep.