11 ms·
Sigaction: see who killed you, and more
- geocar 8y agoThere's a lot of cool kings you can do with sigaction. Trapping sigsegv is a great way to do memory management. void sa(int _,siginfo_t*s,void*__){ mmap((void*)((~PAGESZ-1)&(uintptr_t)s->si_addr),PAGESZ, PROT_READ|PROT_WRITE,MAP_FIXED|MAP_ANONYMOUS,-1,0); } Want automatic logging of a big on-disk data structure? mmap some pages read-only map_private, trapping writes and logging the address (then remapping the page read+write). Then at "save time", write out page contents to a replay log, fsync+fdatasync+fcntl-F_FULLFSYNC, then rewrite the file using pwrite/write. You can also implement big sparse hashmaps this way: Every time a server gets a request, it allocates a cell and tells the client about it. Those cells are read+write. If a cell gets checked in, the server simply decrements the counter, but if there's a page fault (read-only) the server can then take the slower path of finding the server that actually responds to it. And so on.
- pm215 8y agoYou have to be a little careful with trapping SIGSEGV to implement funky behaviour though -- it's easy to write a handler that works for the expected case, but harder to write one that doesn't result in incredibly confusing behaviour if the rest of the program runs into a bug that would normally be reported as SIGSEGV->program core dumps... (Also, mmap() isn't async-signal-safe in POSIX, so this is veering into OS-specifics and reliance on implementation details.)
- geocar 8y ago> it's easy to write a handler that works for the expected case, but harder to write one that doesn't result in incredibly confusing behaviour if the rest of the program runs into a bug that would normally be reported as SIGSEGV->program core dumps... Of course. I only write a glib example because there isn't a lot of space here. > (Also, mmap() isn't async-signal-safe in POSIX, so this is veering into OS-specifics and reliance on implementation details.) close() isn't thread-safe[1], so more people are in OS-specifics and relying on implementation details than we probably realise. I don't know of a single unixish system that gets mmap in a signal handler wrong. [1]: http://geocar.sdf1.org/close.html http://geocar.sdf1.org/close.html
- wahern 8y agoI once implemented a specialized contiguous stack structure that used this trick to automatically reallocate the stack. (The stack was state for a compiler-generated backtracking NFA.) The handler would longjmp to code that realloc'd the stack and restarted the NFA. (No internal pointers so resetting the NFA state and restarting was trivial.) I forget the exact performance improvement, but compared to the original code with explicit boundary checks I think it was several multiples faster. Unfortunately signal handlers are process-wide. These tricks are a bad idea for code that needs to play nice with other code (i.e. libraries, modules, etc) unless you know the process won't be multithreaded or you can own SIGSEGV. In the above hack I used sigaltstack, which is thread-local, to communicate per-thread state to the handler, allowing the scheme to work multi-threaded and to detect unintended faults. (The alt stack memory region used special page-aligned offsets so I could derive a thread-local object that didn't overlap with the signal stack.) Still, such a scheme is too ugly to hide inside a blackbox component which is why these tricks rightly don't see much usage. Your code and my code could have never run in the same process.
- jcranmer 8y ago> Unfortunately signal handlers are process-wide. These tricks are a bad idea for code that needs to play nice with other code (i.e. libraries, modules, etc) unless you know the process won't be multithreaded or you can own SIGSEGV. It's safe to handle processor trap-caused signals, since those are guaranteed to be delivered to the current thread. (SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGTRAP). The trickier part is that you can still send SIGSEGV and the like from another process, which means you have to distinguish between "is this signal caused by the processor or somebody else?" not to mention the general "is this signal in the code where I should handle it." Of course, a saner OS (like Windows) would give you a more stack-based approach to handling hardware traps, so that a function can say "if I, or anything I call, gets a #DE, deliver SIGFPE to this location."
- wahern 8y agoBut an installed signal handler is process-wide. There's no way for a thread to install a signal handler without potentially stepping on the toes of some other thread wanting to install its own handler. Contrast that with signal masks which are thread-local, permitting libraries to set and restore masks without disturbing global process state.
- MawKKe 8y agoAlso the last void* argument of the signal handler points to a ucontext_t object (man getcontext), so you can do something crazy like jumping over a NULL-dereference by modifying the instruction pointer and then "returning" with setcontext().
- deleted 8y ago[deleted]
- cryptonector 8y agoThis is all very slow on modern systems. I would not recommend it. But it's true that these are neat hacks!
- quotemstr 8y agoSignal handlers don't have to be process-wide. See my detailed proposal for fixing the situation here: https://www.facebook.com/notes/daniel-colascione/toward-shareable-posix-signals/10157129032641102/ https://www.facebook.com/notes/daniel-colascione/toward-shar... The trouble is that nobody is actually interested in fixing the core API. When I've raised the issue (and my proposal) with libc maintainers, the response has been a bizarre insistence that signals are somehow illegitimate, that the things people do with signals are wrong, and that everyone should just stop with signals despite there being no viable replacement mechanism. As a result of this head-in-the-sand attitude, we're left with an awful decades old signals API and an absolute mess of ad-hoc libraries (which conflict with each other) to work around the worst of the problems. This is how good systems ossify and eventually die.
- carterschonwald 8y agoMy limited knowledge on this topic seems to indicate that some signals are per thread in terms of handler registration rather than process wide. At least if you register the handler in that thread. Am I incorrect ?
- quotemstr 8y agoSignals are process wide. There is no confusion on this subject; people who claim there is are probably thinking of the delivery of process-directed signals, which is an entirely different subject. There is no current system for which sigaction acts per thread.
- cryptonector 8y agoPOSIX is not clear on this. They are generally process-wide, but on Linux it depends on the pthread library. EDIT: What would be nice is a way to set signal disposition on a process-wide basis as a "default" that could be overridden on a per-thread basis. But there is no flag for distinguishing "the process" from "this thread" in sigaction(2). Another detail to address is what happens when you pthread_create() a new thread from a thread with signal disposition "overrides": does the new thread inherit these, or not? Another detail: on fork(), should the calling thread's disposition "overrides" become the process-wide dispositions for the new process?
- nrclark 8y agoSigaction is pretty cool. Signals still suck though, if you need to stick to the POSIX specs. It's crazy that so many standard library functions will fail with an EINTR errno if your program gets a signal while it's in a library function. Basically any library call that ever does any I/O of any kind (that means common stuff like open/dup/read/write, but also less obvious stuff like getpwuid). Sigaction provides the SA_RESTART flag which _should_ auto-restart most calls, but it doesn't work everywhere. And the functions which do and don't auto-restart aren't documented anywhere that I've ever found. As a result, any high-reliability program that installs signal-handlers basically needs to have a retry mechanism for every libc call that can result in an EINTR. Some functions might not need it, but I've never found a good way to tell which ones do and which ones don't. Select(), for example, doesn't support SA_RESTART. Neither does wait(). And that means they all need to be checked every time, if you're concerned with portability and reliability. And that's nuts. IIf I could change one thing about sigaction in POSIX, it would be to add an SA_NOINTR flag to the sigaction API, which would let any POSIX library calls finish before presenting a signal. That, plus I'd also add Linux's signalfd() to the POSIX standard so that we could do away with "this one weird trick from a DJB" that everybody uses now (and I'd make signalfd use the new SA_NOINTR).
- deleted 8y ago[deleted]
- iforgotpassword 8y agoAgreed. Years ago I wrote a daemon for Linux with glibc only, so I assumed I can just ignore EINTR everywhere, until I needed to use valgrind and suddenly had EINTRs everywhere. Rather than making signalfd POSIX, if we play make-a-wish let's just go for kqueue. Signalfd feels somewhat hackish, even though its simplicity admittedly has some appeal too.
- xenadu02 8y agoSignals are an example of false simplicity: they themselves are very simple at the cost of foisting the complexity on you and every library you use.
- 8y ago