3 ms·
> Also, there's a lot more you can do in a signal handler than just set a flag: you can longjmp or even make calls to regular functions. ... As long as the cal
by arghwhat 3y ago
> Also, there's a lot more you can do in a signal handler than just set a flag: you can longjmp or even make calls to regular functions.
... As long as the called functions are fully async-signal-safe/reentrant. It used to be even more sensitive, in that not all register state was correctly saved/restored on Linux.
(On the fabled plan9, where signals are replaced with arbitrary-text "notes", the issue is even bigger as floating point registers are not saved/restored)
> We should be enhancing POSIX signals to make them easier to share, not casting aspersions on them.
That's fair, but they have already been fixed with signalfd, which more modern processes use to deal with the few POSIX-isms that require it, but many of the original uses have been superseded.
E.g., few modern applications use `timer_create` and SIGALRM, as they can instead use timerfd or poll with strategic timeout (although usually abstracted away by their eventloop). SIGBUS is a POSIX-way to deal with mmap truncation, but sealed memfds can be used to avoid the issue altogether. SIGUSR1/2 can be replaced by any IPC mechanism to give much more flexible controls than a single "reload"/"toggle" signal.
(The POSIX ways can still be useful in certain simpler programs of course.)
- quotemstr 3y ago> As long as the called functions are fully async-signal-safe/reentrant. That is not accurate. If I have a single-threaded program sitting around a ppoll(2) loop and a signal can arrive only inside my main loop ppoll(), then I know a priori that my signal handler can't be interrupting non-reentrant code and so I can call whatever I want inside it. > It used to be even more sensitive, in that not all register state was correctly saved/restored on Linux. Don't confuse architectural flaws with implementation bugs. I'm reminded of an argument on emacs-devel in which someone argued that we couldn't call malloc(3) in a multi-threaded program because one beta version of glibc once had a thread safety bug in the malloc implementation. > SIGBUS is a POSIX-way to deal with mmap truncation, but sealed memfds can be used to avoid the issue altogether. Sealing a file descriptor doesn't physically seal a USB key into a USB port. :-) You also get SIGBUS on IO failures on mmaped files, and surprise removal is an IO failure. There's really no alternative to a synchronous signal here --- a regular file being mmap()ed isn't a userfaultfd. IMHO, I think it's crying shame that Hotspot (last time I checked --- maybe it's fixed?) doesn't watch for SIGBUS on access to MappedByteBuffer and translate surprise USB device file removal into a nice clear VM-level exception. Even for cases for which userfaultfd can work, a synchronous signal can be more efficient because it involves just one entry into the kernel. (sigreturn is optional.) I'd really hate to give up the conventional signal mechanism entirely, although of course I approve of things like signalfd and userfault that reduce the need for signal handling.
- arghwhat 3y ago> That is not accurate. If I have a single-threaded program sitting around a ppoll(2) loop and a signal can arrive only inside my main loop ppoll(), then I know a priori that my signal handler can't be interrupting non-reentrant code and so I can call whatever I want inside it. If you have a single-threaded program sitting in ppoll, you cannot ever receive a SIGSEGV during said ppoll unless you passed it bugus fds, timeout or sigmask pointers. A sleeping process cannot segfault. If you register a SIGSEGV handler, you have zero guarantees that it will only fire at a particular time in your code as it is delivered on any pagefault from any code accidentally generating it. This is why the async-signal-safe rules apply. If you try handling such fault, what needs to be reentrant is the full call stack that lead to the fault, and every call made from the handler. If, for example, the fault is generated from an event loop handler (in case of a single-threaded example, most things run off event loop handlers), the signal handler must in turn not touch the event loop (no adding/removing/adjusting events, no dispatch) unless the event loop is fully reentrant.
- quotemstr 3y ago> you cannot ever receive a SIGSEGV during said ppoll Unless someone sends one with kill(2). Also, I was talking about signals in general, not SIGSEGV in particular. Who uses SIGSEGV as an async work dispatch mechanism? > unless you passed it bugus fds, timeout or sigmask pointers. A sleeping process cannot segfault. No, you get EFAULT. System calls don't work that way. > If you register a SIGSEGV handler, you have zero guarantees that it will only fire at a particular time in your code as it is delivered on any pagefault from any code accidentally generating it That's why you use sigaction(2) to register signal handlers --- your callback gets both a siginfo_t and a ucontext_t you can use to figure out whether your segfault came from a region of code you know about or some other random thing going wrong in your process. In principle, you can do the non-reentrant thing after having checked that the signal came from the context you expect, and you can do this checking in an async-signal-safe manner. > If, for example, the fault is generated from an event loop handler (in case of a single-threaded example, most things run off event loop handlers), the signal handler must in turn not touch the event loop (no adding/removing/adjusting events, no dispatch) unless the event loop is fully reentrant. Of course. That's a matter of program design.