Sun did try multi-thread fork semantics, but that didn't help.
UNIX "fork" started as a hack. The reason UNIX originally used a fork/exec approach to program launch was to conserve memory on the PDP-11. "fork" originally worked by swapping the process out to disk. Then, at the moment there was a good copy in both memory and on disk, the process table entry was duplicated, with one copy pointing to the swapped-out process image and one pointed to the in-memory copy. The regular swapping system took it from there.
Then, as machines got bigger, the Berkeley BSD crowd, rather than simply introducing a "run" primitive, hacked on a number of variants of "fork" to make program launch more efficient. That's what we mostly have today. Plan 9 supported more variants in a more rational way; you could choose whether to share or copy code, data, stack, and I think file opens. The Plan 9 paper says that most of the variants proved useful. But that approach ended with Plan 9. UCLA Locus just had a "run" primitive; Locus ran on multiple machines without shared memory, so "fork" wasn't too helpful there.
Threads came long after "fork" in the UNIX world. (Threads originated with UNIVAC 1108 Exec 8 (called OS 2200 today), first released in 1967, where they were called "activities"). Exec 8 had "run", and an activity could fork off another activity, but not process-level fork. Activities were available both inside and outside the OS kernel, decades before UNIX.
That's why UNIX thread semantics have always been troublesome, especially where signals are involved. They were added on, not designed in.
Not all combinations are allowed. In this specific case, if you specify CLONE_SIGHAND then you must also specify CLONE_VM (so the processes share a virtual memory space, and are essentially threads).
For that reason, clone(2) always felt like an overgenerality -- an attempt to decompose something into orthogonal parts, most combinations of which actually don't make sense.
Even when you have a more formal "run" primitive, you still want a lot of inheritance; file handles (console programs and redirection), security identity and authorizations, current working directory.
There's also the matter of API complexity. If you want to configure the context in which a program runs, there are roughly two different API approaches you can take. You can deeply parameterize the 'run' command, or you can configure the "current" process and then hand that off to the new process. But you usually need the second API (configuring the current process) anyway.
So I don't see fork(), per se, as a particularly crufty API. Slightly more configurability, like clone() or Plan 9 is better.
Multi-threaded signals are pretty dreadful, though. In a multi-threaded system, signals should be delivered on a separate thread, not via an existing thread getting hijacked.
Multi-threaded signals are pretty dreadful, though. In a multi-threaded system, signals should be delivered on a separate thread, not via an existing thread getting hijacked.
That is basically the recommended way to handle signals in a pthreaded program - have one dedicated signal-handling thread that calls `sigwait()` in a loop, and block all signals in the signal masks of the other threads.
"fork()" didn't start as a hack. There is no documentation to suggest that one return from fork() was a copy now on-disk while the other remained in ram.
1906: Call "xswap" (4368) to copy the data segments into the disk swap area. Because the second parameter is zero, the main memory area will not be released.
1907: Mark the new process as "swapped out".
1908: Return the current process to its normal state.
From what I can tell, that code is in a conditional which checks whether the new processes can fit in main memory. If it can fit, it jumps to line 1913.
Anyhow, there's a difference between the fork interface being a hack, and the fork implementation being a hack. Unix is is a cornucopia of implementation hacks. That doesn't mean the interfaces weren't deliberately and thoughtfully designed.
Much like C, what makes Unix unique and still relevant is that the deliberate design took into account practical implementation considerations. Unix and C are most elegant from an engineer's perspective. It's an interesting balance of interface complexity and implementation complexity. This is why some people claim that the Unix design philosophy epitomizes "worse is better".
plan9 rfork() never shares the stack segment.
the other segments can be shared or copied
depending on the RFMEM flag. theres no
special voodoo needed for "thread" local
storage, its just memory reserved at the
top of the stack.
UNIX "fork" started as a hack. The reason UNIX originally used a fork/exec approach to program launch was to conserve memory on the PDP-11. "fork" originally worked by swapping the process out to disk. Then, at the moment there was a good copy in both memory and on disk, the process table entry was duplicated, with one copy pointing to the swapped-out process image and one pointed to the in-memory copy. The regular swapping system took it from there.
Then, as machines got bigger, the Berkeley BSD crowd, rather than simply introducing a "run" primitive, hacked on a number of variants of "fork" to make program launch more efficient. That's what we mostly have today. Plan 9 supported more variants in a more rational way; you could choose whether to share or copy code, data, stack, and I think file opens. The Plan 9 paper says that most of the variants proved useful. But that approach ended with Plan 9. UCLA Locus just had a "run" primitive; Locus ran on multiple machines without shared memory, so "fork" wasn't too helpful there.
Threads came long after "fork" in the UNIX world. (Threads originated with UNIVAC 1108 Exec 8 (called OS 2200 today), first released in 1967, where they were called "activities"). Exec 8 had "run", and an activity could fork off another activity, but not process-level fork. Activities were available both inside and outside the OS kernel, decades before UNIX.
That's why UNIX thread semantics have always been troublesome, especially where signals are involved. They were added on, not designed in.