The evolving Unix attitudes on handling signals in your code
utcc.utoronto.ca
utcc.utoronto.ca
Back when I wrote Python and had to wire up event loops by hand, signalfd seemed to offer a similar experience. Sadly signalfd’s issues are well documented, and it’s really hard to handle signals in complex code bases without help from a runtime like Go’s: https://news.ycombinator.com/item?id=9564975
https://developer.apple.com/documentation/dispatch/dispatchs...
I think it has an amazingly low barrier to entry for dealing with simple parallelism. I can see why it's still the gatekeeper for concurrency in iOS and macOS software given how flexible the API is.
(In particular, being able to pass queues to publishers in Combine is a little bit of sweetness that makes it more of a joy to use)
For process-wide signals you can already have dedicated threads.
Other signals indicate external events which the program ought to do something about, but don't stop current execution. Those can be handled by any thread, and often are routed to some event-handling thread.
Then there's cancellation. If you need to stop a thread, how do you do that? Some operating systems have explicit thread cancellation or inter-thread exceptions.
These are the three main cases. Trying to handle all three through one mechanism has resulted in the proliferation of Linux signal handling modes, flags, and functions.
As I said, I'm not talking about thread-specific signals (exceptions) such as SIGSEGV, just process-level signals such as SIGINT. pthread_mutex_lock() isn't async-signal-safe, so you can't reliably use it from a signal handler, since it can cause undefined behaviour if a thread is in the middle of operating on a mutex, and then gets a signal which tries to operate on the same mutex. Whereas, if the signal handler is on a separate thread, it can use thread-safe-but-not-async-signal-safe APIs such as pthread_mutex_lock(), and it doesn't have to stop any other thread to safely do so
> For process-wide signals you can already have dedicated threads.
You can but it is work to set them up. People would be more likely to do it if there was a simple API, e.g. thread_signal(), which would be like signal() but creates a signal handling thread for given signal and handler, and blocks that signal on all the other threads in the process. Or maybe for sigaction() an SA_OWNTHREAD flag which gives the signal handler its own thread.
These days posix is largely "how does linux do it" just like in the past where it was "how does insert_large_unix_vendor_here do it"
Unix and C are the two greatest technical debts of all time with respect to computing. We are just now beginning to pay off some of those debts with respect to permissions, isolation, hardware access, instability, memory corruption, and remote code execution.
Many of our best practices are more reflective of the mainframes before Unix than Unix.
I think, uncontroversially, it clearly has. Particularly, if you look at what the competing "state of the art" has been, I think POSIX has been a major win.
If you're willing to ignore the cross platform compatibility it brings you, and write your own implementations, you're suddenly presented with all kinds of nice options for managing signals. For example, signalfd(2) is sweet, and there's no reason to think that some version of this couldn't be standardized by POSIX in the future.
Some people see Engineering as a church, where purity is the goal, I see it as a tool, where useful compromise is the goal.
The Macintosh basically collapsed under its own technical debt, and the only way Apple got out of it was buying a Posix-like system and writing a compatibility layer for it (and this Posix-like system became OS X). The kernel, xnu, is purportedly a microkernel, but some of the most useful parts of it are the BSD systems that were Frankensteined in.
The Macintosh had a lot of technical debt prior to the Mac OS X switch. This has nothing to do with being “primarily a network and I/O device”—for what it’s worth, it common to see Macs with networking in the 1990s. Ethernet was standard early on, and before that, you could use something like PhoneNET.
If you take a system that is designed to work within 128K of RAM and an 8 MHz, you make a lot of design decisions that just aren’t appropriate for, say, a system with 256 MB of RAM and a 1 GHz processor. That’s roughly the span of the classic Mac OS, in hardware terms. The original Mac operating system would only run one program at a time (not counting desk accessories), and it made sense to give the program unrestricted access to memory.
After that, how would you introduce protected memory, without breaking userland? That’s a big part of the technical debt that I’m talking about. There were several attempts to introduce protected memory to the Macintosh—A/UX, MkLinux, Copland, Taligent, and Rhapsody. Rhapsody is the one that managed to stick around.
The shear size of the undertaking has meant that we really haven't seen fundamental changes in OS architecture, despite a radically different computing environment. If that is not technical debt, I don't know what is.
mit's its for the pdp-10 (runs in opensimh), ibm's mvs for the s/370 (runs in hercules), smalltalk-80, cp/m-80 with turbo pascal (since you wouldn't want to use bds c), openvms with bliss or pascal, f-83 or other forths, gw-basic — there are lots of options available, and some of them are even free software
there are also more recent alternatives (menuet, templeos with holy-c, reactos, symbian, oberon, risc os, symphonyos, xen, genode, sel4)
nowadays you can run any of these in emulation, you can run them even faster on a softcore in an fpga, and
i think that if you try some of these alternative systems you will come to understand that although most of these systems were superior to unix and c in some way, overall unix and c were part of the solution, not part of the problem
of course, we know a lot of things now that we didn't know 50 years ago when c was born, and one of c's original designers demonstrated how he would redesign c knowing what he knows now; the result was golang
Using C++23 <stacktrace> to get proper crash logs in C++ programs
The evolving "Unix attitudes" became non Unix. Bloat is everywhere in Linux.
I'm not sure what you mean. The point of the article is to explain that older implementations played fast and loose with signal handlers that might do all sorts of operations that turned out to be unsafe. As Chris points out, the current POSIX standard is very constrained! You call some specific functions, you set a flag, you GTFO.
So I don't know what you are on about regarding bloat, because if anything in Unix has slimmed down since V7, it's signal handlers (see also the discussion about Bourne shell exploiting SIGSEGV to allocate memory.)
signals and fork are pretty bad design decisions. but personally I blame the later generations for not being interested or creative enough to explore alternatives. overall unix was pretty cute for the time.
I think a handle-based API would have been superior to fork(). So every API which changes shared process state takes a process handle, which could be either the current process or a yet-to-be-started child process. That would have provided most of the advantages of fork() without the many disadvantages.
I'm sure DonHopkins will be along, endlessly quoting his own contributions to the UNIX-HATERS Handbook, to elucidate.