In Unix and Linux systems, almost everything—regular files, directories, pipes, network sockets, and character devices—is represented as a stream of bytes accessed through a file descriptor (FD).
Standard File Descriptors
By default, every new Unix process starts with three open file descriptors:
| FD Number | Name | Description | Default Target |
|---|---|---|---|
0 |
stdin |
Standard Input | Keyboard / Stream input |
1 |
stdout |
Standard Output | Terminal display |
2 |
stderr |
Standard Error | Terminal display (unbuffered error stream) |
Under the Hood: Per-Process vs Kernel File Table
When your process calls open() or socket(), the kernel doesn’t give user space direct access to internal memory structures. Instead:
- It allocates a non-negative integer in your process’s File Descriptor Table (e.g.,
3,4, etc.). - That entry points to the system-wide Open File Description Table in the kernel, tracking the file offset, status flags (like non-blocking), and access mode.
- That entry points to the file’s inode table entry on the disk.
[Process FD Table] ──> [Kernel Open File Table] ──> [Inode / Device]
FD 3 (stdout/pipe) Offset: 1024, Mode: Read File on disk
Checking Open Descriptors for a Process
You can inspect the file descriptors of any running process in Linux via /proc:
ls -l /proc/<PID>/fd
This simple, unified abstraction makes redirection (>, 2>&1, |) in Unix shells extraordinarily clean, predictable, and composable.