Hacker News new | past | comments | ask | show | jobs | submit login

Eyebrows risen, please elaborate.



Unix's original design dates back from the late 1960s, where processor speed was measured in megahertz and memory size was measured in kilowords. Design choices that were reasonable then do not necessarily translate well to the 21st century.

One example among many warts: in traditional Unix, user programs manipulate process IDs in a shared PID address space, not process handles private to one process (as in file descriptors). Therefore, nearly any operation done on a process through a PID is inherently racy. That's because between acquiring the PID of a process you want to interact with and performing an operation by PID, the PID could no longer refer to the process you were interested in (for example, the process could die and the kernel could recycle the PID for a new, unrelated process). There are ways to fix that (pidfd_open on Linux for example), but the sheer amount of legacy code out there means that these warts will stay around in Unix-like systems for a very, very long time.

Even if you somehow fix every single design issue with Unix and remove all the warts, the end result would not really look like Unix anymore. One example of a legacy-free, capability-based operating system with no implicit ambient authority is Fuchsia, whose kernel interfaces do not resemble traditional Unix syscalls at all.


Everything with Unix sits on the foundation that multiple users from various terminals (requiring different driver implementations) are typing text on them simultaneously. The system consists of little programs that users combine in creative ways to run a process hierarchy. And this is temporally-confined within a session, so nothing of that “playing” should remain running once the session is over, unless explicitly stated with setsid() and friends.


Copying the entire address space into a second process just so it can be overwritten by exec a few instructions later is a big mess, and the implementation problems raised by "what if the program doesn't call exec soon?" are severe. A spawn model is much better for creating new processes.


This is a deliberate design decision. Shells do important stuff in the child process before calling exec - mostly redirecting file descriptors to pipes or files, and setting up environment variables. Exec replaces the program image, but retains these. The beauty of this is that for the exec’d program, IO is still stdin and stdout, no matter if it’s a pipe or a file.


You can do all of that stuff in a spawn model as well.


You can, but the interface of spawn(…) would grow enormous and complex. In the fork/exec model, you can execute arbitrary syscalls affecting the environment of the child, using all the inherited data from the parent.


There's a third possible API model, which is sort of between the spawn() model (everything for the new process configured in a single call) and the fork/exec model (the child process can run arbitrary code to set up the new process): the process creation API could create the new process in a "suspended" state (like CREATE_SUSPENDED does on Windows), the parent process could then manipulate the new process as desired, and finally tell the kernel to start it.


The vfork syscall first appeared in BSD 3.0 and did it the other way around. When calling vfork, the parent is suspended until the child terminates or calls exec. The child is given read-only access to the parent's memory space, or it traps on an attempt to write.

This is how they optimised it in the days before COW, by fully preserving the intended behavioural semantics of fork/exec. You shouldn't modify memory in the child and the OS therefore shouldn't copy the memory space.


You could "spawn" a separate minimal binary that only has to set up the proper environment, then have that binary "exec" into the intended process.


But that "minimal" binary would be a static and independent piece of code nevertheless. It wouldn't have access to the memory space of the parent so as to work with the already open descriptors without probing for them, it cannot use data already processed by the parent in the form of paths, etc.

Essentially you run into the same problem - now the complexity of the spawn(...) interface is shifted to that binary.


> It wouldn't have access to the memory space of the parent so as to work with the already open descriptors without probing for them, it cannot use data already processed by the parent in the form of paths, etc.

You could simply use ordinary IPC for these things, though. They need not be implemented as part of the OS.


Job control was designed before SMP was generally available.

Processor affinity, sequential execution on available/shared resources, hold queues for non-fatal errors, and policy enforcement are missing from job control.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: