xeyes wrote:My thoughts on this topic:
1. CreateProcess() is a fine way. It is clearly defined and thus highly resistant to abuse.
fork() has no arguments, CreateProcess() on Windows has around a dozen
Most of CreateProcess()'s parameters are optional. Let's also not forget that execve/execvp/... also have lists of parameters,
The maximum set of arguments taken by any exec family function is equivalent to the arguements lpApplicationName, lpCommandLine, and lpEnvironment to CreateProcess(). The command line is broken up into individual args, and in the execl family those are a variadic argument list instead of an array as they are for the execv family, but either way it's just a different way of passing the same information passed in lpCommandLine.
and a dozen more different calls (like fnctl or dup and open/close pipe create to redirect STDIO, or the myriad of [set/get][r/e][xyz]id calls needed to describe security contexts) are needed to replicate what CreateProcess() can do in a single shot.
That's not a bug, it's a feature
. The process of creating a new process (or starting a new program in the same process; exec need not only occur after fork(), see below) can be broken down into individual elements, each of which can be omitted if not needed.
As an example of exec'ing without fork(), consider the following, from my own machine:
Code: Select all
% ps
PID TTY TIME CMD
2432424 pts/26 00:00:00 zsh
2432431 pts/26 00:00:00 ps
% exec bash
$ ps
PID TTY TIME CMD
2432424 pts/26 00:00:00 bash
2432479 pts/26 00:00:00 ps
2. vfork() does invite a lot of abuses and documentations on how tolerate should the kernel be towards abusing seems to be system dependent, like Oracle's are different from Linux documentation on this. This creates ample opportunities for the kernel and the programs both trying (sometimes non standard) things in order to best accommodate each other and many times actually stepping on each other's toes while trying to be nice. fork() is fine though.
vfork() is a red herring. It was a kludge adopted by BSD in the interim between PDP-11 Unix (where address spaces were small and fork() without COW wasn't hugely problematic) and the introduction of COW on VAX BSD. The man page for vfork() in 4.2 BSD warned that it was a temporary kludge and would eventually be made synonymous to fork(). It was made synonymous to fork() in 4.4 BSD. Since then, several systems have resurrected it for performance/memory reasons that might be useful on embedded systems, or back in the 90s, but POSIX deprecated it and eventually removed it, and allowed it to be identical to fork(). For maximum portability, it's best to assume that it's identical to fork(), and to avoid using it if possible(). If you're implementing a new OS with a unix or unix-like API, vfork() can be left out or can be an alias for fork().
My biggest beef with the UNIX way of doing this is about the execve family. Every time I think about it, the image that some sort of parasite has done consuming its host and bursts out into the open with the host's flesh and blood flying around just appears vividly before my eyes
Makes one wonder did guy who originally design this method get his idea from some horror movie?
Eh. I'd say it's more "body-snatchers" than "Alien". The host doesn't explode, it just starts acting like the parasite.
3. Process in the traditional sense is already a very big concept/scope in computing. Few processes have any legitimate need to keep creating and killing them (shells, service managers/init or other session managers obviously have to do this, "process drivers" like make need this, or some really fancy and huge programs might have their own pre-loaders or accompany processes, can't easily think of others that have a strong need for this). Thus I feel that it is their responsibility to understand and use the available utilities correctly, in exchange for the great power of managing the life and death of their likes. Namely, it's not the kernel's business to handhold them.
To me, a big monolithic function like CreateProcess() feels more handholdy than a fine grained fork+exec method.
A lot of games have subsystems that could be separated from the game into separate executables, as they only operate during one part of the lifecycle of a playthrough, are otherwise inactive, and generate data that's used later on user-interaction timescales (i.e, the game is going to spend the intervening time waiting for the user, so tight coupling for performance isn't critical). For example, consider a typical 4x: Map generation can be split off into a separate executable (and indeed, many games due have separate map editors and then duplicate part of the map editor functionality in the main game). Even randomly generated maps can often be saved for use in a later game. Specifying the player empire can be spun off into a separate executable: it's done once at the beginning of play, the output is often saved for later use, etc. It's not typically done, but it's an example of the kind of program that could benefit from creating child processes that doesn't fall into one of the categories you supplied. I would argue that if a component of a program can be split off into a separate executable, it generally should be, as otherwise a pointer bug in one module can screw up the heap for the whole program.
I think you'd be surprised, too, just how many shell instances run in the background on a typical Unix system. Under traditional init systems, all of your service management is done through shell scripts. Systemd has changed that somewhat on Linux, but it's deeply divisive, and isn't a thing on other *nixes.