All four of /dev/fd/0
, /dev/stdin
, /proc/self/fd/0
and /dev/pts/2
are file names, as are /////dev/../dev/fd//0
, /bin/sh
, /etc/fstab
, /fioejfoeijf
, etc. All but that last example are likely to be the name of an existing file on your machine. A file name is a string that can designate a file on your filesystem; under Linux, any string that does not contain a null byte and that is at most 4096 bytes long is a valid file name. Many of these names are equivalent, e.g. /bin/sh
is equivalent to ///bin/sh
, /bin/../bin/sh
(assuming /bin
is an existing directory), etc. All the examples I've given so far are absolute file names; there are also relative file names, which do not begin with a /
and whose meaning depends on the current directory.
The terminology surrounding file names isn't universal; sometimes “file name” means a full path to a file, and sometimes it means the name of a directory entry. The POSIX terminology is “filename” or “pathname component” for the name of a directory entry, and “pathname” for a full path.
A file descriptor designates an open file in a particular process. The kernel maintains a table of file descriptors for each process. Each entry in the file descriptor table indicates what to do if the process requests reading, writing and other operations on the file descriptor.
File descriptors may correspond to a file and have an associated name, but not all of them do. For those that do, the file may be a regular file, a directory, a device file or a named pipe (also called FIFO) (the kind created by mkfifo
); some systems have further possibilities such as Unix sockets and doors. Examples of file descriptors that don't have an associated named file include pipes (the kind created by the pipe
system call) and networking sockets.
/dev/fd/0
, /dev/stdin
and /proc/self/fd/0
are file names (all equivalent) with a peculiar meaning: they all designate whichever file is currently accessed via file descriptor 0. When a process opens these, the kernel copies the entry with index 0 in the file descriptor table to a new descriptor. Opening any of these files is equivalent to calling dup(0)
. The named files are a way to indirectly get a process to use one of its already-open files rather than open a new file; they are mostly useful to pass on a program's command line, where the program expects the name of a file to open.
In the C API, system calls return a negative value to indicate an error, and the error code in errno
gives more information on the nature of the error. Your man page should explain the possible errors on your system. There are two standard error codes:
EAGAIN
indicates that the new process cannot be created due to a lack of available resources, either insufficient memory of some kind or a limit that has been reached such as the maximum number of processes per user or overall.
ENOMEM
indicates that the new process cannot be created due to a lack of memory of some kind. Under Linux, ENOMEM
indicates a lack of kernel memory, while a lack of userland memory is reported as EAGAIN
. Under OpenBSD, ENOMEM
is used for a lack of userland memory as well.
In summary, fork
can fail due to a lack of available resources (possibly in the form of an artificial limit rather than a simple lack of memory).
The behavior of shells when fork
fails is not specified by POSIX. In practice, they tend to return different error statuses (1 in pdksh, 2 in ash, 254 in bash, 258 in ksh93, 1 in tcsh; zsh returns 0 which is a bug). If you're implementing a shell for production use (as opposed to a learning exercise), it might be worth discussing this on the Austin Group mailing list.
Best Answer
The problem is described there in your source,
select()
should be interrupted by signals likeSIGCHLD
, 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 byselect()
. Watching file descriptors is whatselect()
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 withselect()
.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.)