Linux System Programming · intermediate · ~10 min

File descriptors

Use the int-based POSIX I/O layer.

Overview

A file descriptor is a small non-negative integer that names an open file (or socket, pipe, device) inside the kernel. Programs start with three open: 0 (stdin), 1 (stdout), 2 (stderr). open creates more; close releases them.

Why it matters

Below fopen lives a thinner, more powerful API: raw file descriptors. They are how the kernel exposes every I/O object — files, pipes, sockets, terminals — through a uniform integer handle. To write a shell, a server, or anything that fuses streams, you must speak the file-descriptor layer.

Core concepts

Integer handle. Each process has a per-process table indexed by fd. Inheritance. fork duplicates the table — child and parent share open files. dup2 redirects an fd: dup2(pipe_fd, 1) replaces stdout with a pipe. Closing. Every open needs a close; leaks burn through your process's fd limit (default 1024).

Syntax notes

#include <fcntl.h>
#include <unistd.h>
int fd = open("file.txt", O_RDONLY);
if (fd < 0) { perror("open"); return 1; }
char buf[4096];
ssize_t n = read(fd, buf, sizeof buf);
close(fd);

Lesson

A file descriptor is a small non-negative integer identifying an open file, socket, or pipe. By convention 0 is stdin, 1 is stdout, 2 is stderr. POSIX open / read / write / close operate on file descriptors directly — the layer that <stdio.h> is built on top of.

When you need precise control (non-blocking I/O, polling many fds, sockets), drop down to fds. Otherwise, stdio is friendlier.

Code examples

#include <fcntl.h>
#include <unistd.h>
int fd = open("data.bin", O_RDONLY);
char buf[4096];
ssize_t n = read(fd, buf, sizeof buf);
close(fd);

Common mistakes

  • Forgetting to close fds — eventually you hit EMFILE.
  • Mixing FILE * and int fd for the same underlying file without fflush — data can be reordered.

Debugging tips

lsof -p <pid> lists every open fd of a process — invaluable when you've leaked sockets. strace -e trace=openat,close ./prog shows every open/close syscall. /proc/<pid>/fd/ directly lists the descriptors on Linux.

Memory safety

fd leaks aren't memory leaks but resource leaks — same eventual outcome (program fails) but invisible to valgrind. Every open needs a close. Wrap risky paths with if (fd >= 0) close(fd); cleanup blocks.

Real-world uses

Shells (dup2 to redirect), web servers (one fd per client connection), tee (write to multiple fds), select/poll/epoll (multiplex many fds), Docker (pivot_root keeps the original root fs reachable via an fd).

Practice tasks

  1. Use open/read/close to read the first 100 bytes of a file. 2. Use dup2 to redirect stdout to a file before calling printf. 3. Open the same file twice — observe you get two distinct fds.

Summary

File descriptors are the kernel's uniform handle for every I/O object. Open, use, close — and never leak them. They are the foundation on which fopen, pipes, sockets, and the entire Unix I/O philosophy are built.

Practice with these exercises