Greg wrote:
I guess pipe(2) kind of started this mess, [...]
Maybe I'm
going to far with thinking pipe() could/should have just been a library
call that used open(2) internally, perhaps connecting the descriptors by
opening some kind of "cloning" device in the filesystem.
At times I’ve been pondering this as well. All of creat/open/pipe could have been rolled
into just open(). It is not clear to me why this synthesis did not happen around the time
of 7th edition; although it seems the creat/open merger happened in BSD around that time.
As to pipe(), the very first implementation returned just a single fd where writes echoed
to reads. It was backed by a single disk buffer, so could only hold ~500 bytes, which was
probably not enough in practice. Then it was reimplemented using an anonymous file as
backing store and got the modern two fd system call. The latter probably arose as a
convenient hack to store the two file pointers needed.
It would have been possible to implement the anonymous file solution still using a single
fd, and storing the second file pointer in the inode. Maybe this felt as a worse hack at
the time (the conceptual split in vnode / inode was still a decade into the future.)
With a single fd, it would also have been possible to have a cloning device for pipe’s as
you suggest (e.g. /dev/pipe, somewhat analogous to the implementation of /dev/stdin in
10th edition). Arguably, in total code/data size this would not have been much different
from pipe().
My guess is that from a 1975 perspective, creat/open/pipe was not perceived as something
that needed fixing.