The fd table
Every process has a file descriptor table — an array maintained by the kernel, indexed by small non-negative integers. Each entry points to an open file description (the kernel's object tracking an open file: its position, flags, and the underlying file or resource).
When you call open, the kernel creates a file description and returns
the lowest available index in your fd table — that index is the file descriptor.
When you call read(fd, ...) or write(fd, ...),
the kernel looks up entry fd in your table to find the file description,
then performs the I/O.
The three standard descriptors
Every process starts with three fds already open, set up by the shell (or whoever
launched the process) before calling exec:
| fd | name | C constant | default target |
|---|---|---|---|
| 0 | stdin | STDIN_FILENO | terminal keyboard |
| 1 | stdout | STDOUT_FILENO | terminal screen |
| 2 | stderr | STDERR_FILENO | terminal screen |
./prog > output.txt replaces fd 1 with a file while fd 0 still reads
from the terminal.
open and close
open will return -1
with errno == EMFILE. Use lsof -p <pid> to inspect
a process's open fds.
dup and dup2: the mechanism behind redirection
dup2(oldfd, newfd) makes newfd refer to the same open
file description as oldfd. If newfd is already open,
it's closed first. This is the atomic operation that shells use to implement
> and | redirection.
After dup2(fd, 1), both fd and 1 point to
the same file description. Closing fd leaves the description open —
it's only freed when all descriptors pointing to it are closed.
The reference count on the file description decrements with each close.
Fds are inherited across fork
When a process forks, the child inherits a copy of the parent's fd table. Both parent and child share the same open file descriptions — including their file positions. This is how pipes work: the parent creates a pipe (two fds: read end and write end), forks, and each process closes the fd it doesn't need. You'll see this in the pipes lesson.
A file descriptor is just an integer index into a per-process table — and dup2 lets you rewire any fd to any open file, which is how shells implement redirection.