Why should fork() have been designed to return a file descriptor?

The problem is described there in your source, select() should be interrupted by signals like SIGCHLD, but in some cases it doesn't work that well. So the workaround is to have signal write to a pipe, which is then watched by select(). Watching file descriptors is what select() is for, so that works around the problem.

The workaround essentially turns the signal event into a file descriptor event. If fork() just returned an fd in the first place, the workaround would not be required, as that fd could then presumably be used directly with select().

So yes, your description in the last paragraph seems right to me.

Another reason that an fd (or some other kind of a kernel handle) would be better than a plain process id number, is that PIDs can get reused after the process dies. That can be a problem in some cases when sending signals to processes, it might not be possible to know for sure that the process is the one you think it is, and not another one reusing the same PID. (Though I think this shouldn't be a problem when sending signals to a child process, since the parent has to run wait() on the child for its PID to be released.)

It's just a musing on the lines of "it would be great if Unix was designed differently than it is".

The problem with PIDs is that they live in a global namespace where they could be reused for another process, and it would be nice if fork() returned in the parent some kind of handle that would be guaranteed to always refer to the child process, and that it could pass to other processes via inheritance or unix sockets / SCM_RIGHTS[1].

See also the discussion here for a recent effort to "fix" that in Linux, including adding a flag to clone() which will cause it to return a pid-fd instead of a PID.

But even then, that would not eliminate the need for that self-pipe hack [2] or better interfaces, since the signals notifying a parent process about the state of a child are not the only ones you would like to handle in the main loop of the program. Unfortunately, things like epoll(7) + signalfd(2) on Linux or kqueue(2) on BSD are not standard -- the only standard interface (but not supported on older systems) is the much inferior pselect(2).

[1] Preventing the PID from being re-cycled by the time the waitpid() syscall had returned and its return value was used could probably be achieved on newer systems by using waitid(.., WNOWAIT) instead.

[2] I would not comment on D.J. Bernstein claim that he invented it (sorry for the apophasis ;-)).

Bernstein doesn't give much context for this "Right Thing" remark, but I'll hazard a guess: having fork(2) return a PID is inconsistent with open(2), creat(2) etc returning file descriptors. The rest of the Unix system could have done process manipulation with a file descriptor representing a process, instead of a PID. A system call signalfd(2) exists, which allows a somewhat better interaction between signals and file descriptors, and shows that a file-descriptor-representing a process could work out.