An important feature would be missing if
pipe returned a single file descriptor to be used for both reading and writing: there would be no way to signal EOF.
With pipes as they are, the read fd sees EOF when the last copy of the write fd is closed. With hypothetical single-fd pipes, you'd need an extra syscall, like
shutdown for sockets, but applied to a pipe. (Remember that pipes are a older than sockets, and
shutdown didn't exist before sockets.)
And who would be responsible for calling this
pipe_shutdown() syscall? Suppose you do this:
grep foo /etc/passwd | head
If there's only a single fd, which
head have both inherited, then somehow when
head needs to receive an EOF. But the fd held by the
head process is writable, so the kernel doesn't know for sure that nothing more will be written to the pipe. You may be thinking: process exit should automatically send EOF on all pipe fds held by the process. But that'll break a lot of stuff, like
( echo FOO ; grep foo /etc/passwd ) | more
echo writes to the pipe and exits, and we don't want an EOF to happen yet.
grep is still going to write to the pipe. And that problem isn't limited to the "obscure" case of parenthesized subshells. It can happen any time a shell script is used on the left side of a pipe.
If you'll think it over, the pipe EOF mechanism really requires a distinction between fds that can write to the pipe and fds that can only read it. And that's not dependent on how many processes are involved.