There was also no form of interprocess communication other than the filesystem, nor any synchronization other than waiting for a specific process to exit. So having multiple processes work on the same problem would mostly have been very awkward.
Until Doug McIlroy invented pipes. And the modern Unix shell pipeline style of creating complex programs.
I thought the fork syscall was the only way to spawn new programs in earlier versions of Unix, and that the spawn syscall only came later. Is that not right? If there was no other syscall, fork must have been designed for this purpose.
Parallelism? In the original unix? You're talking absolute nonsense. These were single socket, single core systems. How on earth do you think it was designed for parallelism?
But we don't even need to debate this - read the 1971 manual.
https://www.bell-labs.com/usr/dmr/www/pdfs/man21.pdf
"fork is the only way new processes are created"
exec is also in there, and refers to fork and describes how to use them to run programs.
The suggestion that it was done this way because the expected main use case was a fork without an exec at least provides a rational explanation (I don't think 'threads' as distinct from processes existed yet in unix? a fork without an exec was the closest thing you had), but I have no idea if it's historically accurate. Would be interesting to find people who were around and get some background.
Ah, this is interesting:
> Then came Unix, in the early 1970s. The Unix notion of a “process” became a sequential thread of control plus a virtual address space (incidentally, the Unix notion of a process derived directly from the Multics process design [Saltzer, 66]). So “processes”, in the Unix sense, are quite heavyweight machines. Since they cannot share memory (each has its own address space), they interact through pipes, signals, etc). Shared memory (also a rather ponderous mechanism) was added much later.
> After some time, Unix users started to miss the old processes that could share memory. This led to the “invention” of threads: old-style processes that shared the address space of a single Unix process. They also were called “lightweight”, by way of contrast with “heavyweight” Unix processes. This distinction dates back to the very late 1970s or early 1980s, i.e. to the first “microkernels” (Thoth (precursor of the V-kernel and QNX), Amoeba, Chorus, the RIG-Accent-Mach family, etc).
http://www.serpentine.com/blog/threads-faq/the-history-of-th...
Not totally sure what light that sheds on the question, if any, heh.
I'm not familiar with the guts of threading libs, but AFAIK the syscall that spawns threads is clone(), which takes a function pointer and a stack pointer, so it very much does not start "with the same data structures and the same execution point".
This is all fine, there are two scenarios, neither of which is the imagined "race" described above:
1. "Fork-and-exec" Our new execution context will actually run a different program. We immediately do so, the new program doesn't care about most of the state of the old program, it throws that state away, including locks that were held by the old program's allocator.
2. "New Thread" Our new context will share all the state, if another thread has a process-wide lock for some reason, it can and should apply to us too. If it took a thread-specific lock, we're not that thread, so we already don't care.
Huh? I saw that the manpage for clone() is in section 2, which AFAIK means it's kernel API, whereas C library functions are usually in section 3. Am I mistaken?
Even syscalls(2) refers to clone(2) in its list of syscalls.
You are correct that the manual section 2 is "for" system calls, but in practice today these are just the lowest level of the C library, and not the actual system calls.
My copy of the clone(2) manual page hints at this by commenting /* For the prototype of the raw system call, see NOTES */
and more explicitly in its body text by saying "The main text describes the wrapper function; the differences for the raw system call are described toward the end of this page."
Other modern stuff is the same way, the section 2 documentation is describing what an ordinary C programmer needs to know, but if you're say, implementing the C standard library or writing a compiler for a language that runs on the bare metal you need to read the kernel documentation which is not provided as manual pages.
I would argue that (a) was easily the most important aspect of fork(). One can always get (b) by spawning more instances of the same program and passing them whatever state is to be shared. But (a) needs a decent API and to be kernel-side if one has no fork(). Yes, posix_spawn() is better, but fork() enabled Unix to kick the can of standardizing a spawn API that doesn't suck until enough experience was had with it to make such a thing.
I really doubt this. Is there an instance of a program with 1st edition lineage that use processes to achieve concurrency? There were pipes, sure, but that could be achieved with a spawn-like call.
I don’t see processes being used for concurrency in UNIX history until BSD sockets and forking servers emerged.
On the other hand, shell pipelines were used for concurrency before BSD added networking code. And I have doubts that shell pipelines would have developed without the fork()/exec() model---see the author's Windows NT (and earlier) with really, seriously broken shell models.
For this purpose, the choice to start with the same data structures and the same execution point is quite sensible - it's essentially what happens when you create a thread nowadays. So, the hypothetical discussion where someone decided to copy the address space in order to launch new programs, would never have happened.
The day where someone decided to reuse the existing fork code to launch processes for new programs, that one must have been worth to watch.