OSwhatever wrote:My question is if this is good design.
That is a good question. The limitations are numerous: Tasks can't use the part of their stack below the stack pointer (or, with AMD64, 128 bytes beyond the stack pointer), since at any time a signal frame may appear there. Tasks are also extremely limited in signal handlers and can only execute async-signal-safe code there (or alternatively, only execute async-signal-safe code in the main task, then the signal handler can do whatever). The most significant limitation here is on memory allocation, which is just not possible in such context, but also calling functions such as printf() is off-limits. Also, as I recently found out on the musl mailing list, you must actually take care not to modify errno, which is easy to do even in async-signal-safe code. So you must save errno at the start of the signal handler and restore it at the end.
Also there is the small matter of tasks crashing because you upgraded the CPU. See, the signal frame must contain the FPU registers along with the CPU regs. But Intel has come out with the AVX-512 instruction set, which increases the size of the register file to 2kB. It is entirely possible that a task does not necessarily plan for that much extra space on all subthread stacks. In particular, some versions of glibc set the minimal stack size at 2kB, so now there are programs out there allocating only 2kB for their smallest threads, which was enough prior to AVX-512 but is no longer enough after it. And while it is possible to only enable AVX-512 for processes that need it, I have been warned that doing so (which requires reprogramming a register on each task switch) will lead to performance degradation.
Beyond that, the process of signalling any PID of a process other than one of your own child processes is extremely error prone, and can lead to the delivery of signals to the wrong process. That is because the parent process controls the life time of the PID. If the process exits, and the parent process reaps it before clearing the PID out of whatever storage it is in (which will definitely happen if the parent also exits, and the child is reparented below PID 1), then you get a stale PID. Which can also be assigned to a new process, and now you are signalling the wrong process.
However, in return, you get an extremely general and versatile mechanism to have any process (and indeed the kernel) tell any other process about any kind of event, from "you just accessed the wrong address" to "you are about to run out of CPU time, better do something about that", to "I just published some new results, so maybe go do something with them". It's one mechanism that does all of that, and I cannot think of any other way to accomplish all of those tasks in a structured manner. In particular for the IPC, being able to quickly signal any process from any process scales better than any event pipe mechanism you can ever come up with (although the DBus people have tried).
Small point of order: That's
POSIX signal mechanism, not Linux. Linux is merely one of many implementations of POSIX.