Linux Process Basics, Task Structs, and Advanced File Descriptors
Introduction: Understanding Processes and the Kernel
When you run ps aux on a Linux system, you see a snapshot of everything currently running:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 39580 13992 ? Ss Jan31 1:27 --/usr/lib/systemd/systemd --switched-root --system --deserialize=56 rhgb
NOTE
- PID 1: The first userspace process started by the kernel (
systemd), which acts as the manager of users, processes, and service states. - PID 2 (
kthreadd): The parent and manager of all kernel threads. Any process or thread wrapped in brackets[...]is a kernel thread—it runs entirely in kernel space and has no backing executable on disk. Process 2 starts systemd with pid 1. which in turn loads other processes - VSZ & RSS: VSZ represents total virtual memory size (including mapped files, shared libraries, and unused space), while RSS (Resident Set Size) reflects actual physical RAM usage.
- Modern system have shifted to using pidfd instead of pid to overcome the race condition problem pid had.
Process State Flags (STAT)
The process state dictates what the process is currently doing:
D: Uninterruptible sleep (typically waiting for I/O).I: Idle kernel thread.R: Running or runnable on the run queue.S: Interruptible sleep (waiting for an event to complete).T: Stopped by job control.Z: Zombie process (terminated but not reaped by its parent).
The Process Control Block: task_struct and mm_struct
Inside the kernel, every process and thread is represented by a task_struct instance (defined in include/linux/sched.h).
struct task_struct {
/* Scheduling */
volatile long state; // TASK_RUNNING, TASK_INTERRUPTIBLE, etc.
unsigned int flags; // PF_EXITING, PF_KTHREAD, etc.
int prio;
/* Identity */
pid_t pid; // thread ID (what the kernel calls pid)
pid_t tgid; // thread group ID (what userspace calls pid)
struct task_struct *group_leader; // points to main thread
/* Memory */
struct mm_struct *mm; // userspace memory descriptor (NULL for kthreads)
struct mm_struct *active_mm; // mm currently in use
/* Filesystem & Resources */
struct fs_struct *fs; // root, cwd
struct files_struct *files; // open file descriptors
/* Signals & Credentials */
struct signal_struct *signal;
const struct cred *cred;
/* Namespaces & Thread Architecture */
struct nsproxy *nsproxy; // mount, uts, ipc, pid, net namespaces
struct thread_struct thread; // arch-specific: registers, FPU state, TLS
};
Do note that process sees the Virtual Memory layout, not the real mapping in ram
Memory Layout (mm_struct)
The mm_struct manages the virtual address space layout of a process:
struct mm_struct {
struct maple_tree mm_mt; // VMA tree tracking memory regions
unsigned long mmap_base; // base of mmap region
unsigned long task_size; // size of task VM space
pgd_t *pgd; // page global directory (top of page table)
unsigned long start_code, end_code;
unsigned long start_data, end_data;
unsigned long start_brk, brk; // heap
unsigned long start_stack;
atomic_t mm_users; // # of processes using this mm
atomic_t mm_count; // reference count
};
- Processes vs. Threads: Cloning a process creates a distinct memory structure (
mm_struct), whereas cloning a thread shares resources like memory and file tables.
File Descriptors: The Unix Abstraction
A file descriptor is simply an integer index pointing to an entry inside a process’s file descriptor table. Each entry acts as a pointer to an underlying kernel object.
Process
+-------------------------+
| FD Table |
|-------------------------|
|0 -> kernel object A |
|1 -> kernel object B |
|2 -> kernel socket |
|3 -> kernel file |
|4 -> kernel pipe |
+-------------------------+
Because diverse resources (sockets, files, pipes, timers) expose uniform system calls (read, write, close, poll, fcntl), the kernel can treat them interchangeably.
Advanced File Descriptors & Notification Subsystems
Modern Linux architectures lean heavily on specialized file descriptors that integrate directly into event-driven loops.
1. epoll
epoll is Linux’s scalable I/O event notification facility. Traditional methods like select() and poll() require scanning every monitored descriptor on every call, creating $O(N)$ overhead.
epoll bypasses this by maintaining a kernel-side ready queue. When an event fires, the kernel places the descriptor directly into the ready list.
- Lifecycle: Create via
epoll_create1(), register targets withepoll_ctl(), and wait usingepoll_wait(). - Versatility: It can monitor sockets, pipes, eventfds, timerfds, signalfds, and inotify descriptors uniformly.
2. eventfd
eventfd is a lightweight signaling mechanism for threads or processes. It functions as a kernel-maintained 64-bit counter:
- Writing an integer adds to the counter.
- Reading resets the counter to zero (blocking if the value is zero).
- Use Case: A worker thread finishes a task and writes
1to an eventfd. An event loop blocked inepoll_wait()wakes up instantly without the overhead of pipes or signals.
3. signalfd
Traditional signals (SIGINT, SIGTERM) interrupt process execution asynchronously, creating complex race conditions. signalfd converts signals into readable streams:
- Block the desired signals using
sigprocmask(). - Create a signalfd associated with those signals.
- Read structured
signalfd_siginfodata directly from the descriptor inside your event loop.
4. timerfd
timerfd transforms timers into regular file descriptors using timerfd_create() and timerfd_settime(). When the configured time elapses, the descriptor becomes readable, returning the number of expirations. This eliminates the need for dedicated timer threads or periodic polling loops.
5. inotify
inotify tracks filesystem modifications without directory polling. By adding watches via inotify_add_watch(), the kernel appends inotify_event records to an internal queue whenever files are created, modified, moved, or deleted, allowing text editors, build systems, and file sync tools to react instantly.
Putting It All Together: The Unified Event Loop
Imagine a high-performance server managing hundreds of thousands of concurrent connections:
epollmonitors client network sockets.timerfdhandles session timeouts and heartbeats.signalfdlistens forSIGTERMfor graceful shutdowns.eventfdwakes the main loop when worker threads finish background jobs.inotifywatches the configuration directory for live updates.
By calling epoll_wait() on a single unified structure, the kernel handles the underlying complexities, waking the process only when real work arrives.
