Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

procfs (/proc Filesystem)

Introduction

The proc filesystem (procfs) is a special pseudo-filesystem in Linux that provides a window into the kernel’s internal data structures. Mounted at /proc, it doesn’t represent actual files on disk — instead, it dynamically generates content when files are read and accepts input when files are written. procfs is the primary mechanism for userspace tools to query and configure kernel parameters, process information, hardware details, and system statistics.

Originally derived from Unix’s /proc (which only contained process information), the Linux version has expanded enormously to encompass kernel tunables, network statistics, memory management parameters, device information, and much more. Tools like ps, top, free, lscpu, and sysctl all read from /proc.

Architecture

Mounting

# View proc mount
$ mount | grep proc
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)

# Mount options
# hidepid=0: All /proc/PID visible to all users (default)
# hidepid=1: Hide processes owned by other users
# hidepid=2: Like 1, but also hide /proc/PID directories
# gid=GID: Group that can access all PIDs regardless of hidepid
$ sudo mount -o remount,hidepid=2,gid=proc /proc

Implementation

procfs is implemented as a kernel module (fs/proc/). Each file/directory under /proc is backed by kernel functions that generate content on demand:

graph TD
    subgraph "procfs Implementation"
        A["/proc entry"] -->|"read()"| B["seq_file interface"]
        B --> C["Kernel function<br>generates output"]
        A -->|"write()"| D["Kernel function<br>parses input"]
    end

    subgraph "Examples"
        E["/proc/cpuinfo"] -->|"show()"| F["print_cpuinfo()"]
        G["/proc/sys/vm/swappiness"] -->|"proc_handler"| H["proc_dointvec()"]
        I["/proc/pid/status"] -->|"show()"| J["task_state_to_char()"]
    end

The proc_dir_entry structure represents each entry:

struct proc_dir_entry {
    unsigned int        low_ino;
    umode_t             mode;
    nlink_t             nlink;
    kuid_t              uid;
    kgid_t              gid;
    loff_t              size;
    const struct proc_ops *proc_ops;  /* Operations */
    const struct inode_operations *proc_iops;
    union {
        const struct seq_operations *seq_ops;
        int (*single_show)(struct seq_file *, void *);
    };
    proc_write_t        write;
    void                *data;
    unsigned int        state_size;
    unsigned int        parent_ino;
    atomic_t            count;
    struct rb_node      subdir_node;
    struct rb_root      subdir;
    struct proc_dir_entry *parent;
    struct rb_root      name_rb;
    /* ... */
};

/proc/pid/* — Process Information

Each running process has a directory /proc/<pid>/ containing detailed information:

$ ls /proc/self/
attr/        cmdline      comm         coredump_filter  cpu/
cwd -> /proc exe -> /usr/bin/ls  fd/     fdinfo/     gid_map
io           limits       map_files/   maps         mem
mountinfo    mounts       net/         ns/          oom_adj
oom_score    oom_score_adj  pagemap   personality  root -> /
sched        sessionid    setgroups    smaps        smaps_rollup
stack        stat         statm        status       syscall
task/        timers       timerslack_ns  uid_map    wchan

Key Process Files

/proc/pid/status — Process Status Summary

$ cat /proc/self/status
Name:   cat
Umask:  0022
State:  R (running)
Tgid:   12345
Ngid:   0
Pid:    12345
PPid:   12340
TracerPid:  0
Uid:    1000    1000    1000    1000
Gid:    1000    1000    1000    1000
FDSize: 256
Groups: 4 24 27 30 46 109 1000
NStgid: 12345
NSpid:  12345
NSpgid: 12345
NSsid:  12340
VmPeak:   132456 kB
VmSize:   132456 kB
VmLck:         0 kB
VmPin:      1234 kB
VmHWM:      4567 kB
VmRSS:      4567 kB
RssAnon:    1234 kB
RssFile:    3333 kB
RssShmem:      0 kB
VmData:    12345 kB
VmStk:       136 kB
VmExe:        64 kB
VmLib:      2048 kB
VmPTE:        88 kB
VmSwap:        0 kB
Threads:    1
SigQ:   0/12345
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000000000
SigCgt: 0000000000000000
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
NoNewPrivs: 0
Seccomp:    0
Seccomp_filters: 0
Speculation_Store_Bypass:   thread vulnerable
Cpus_allowed:   ffffffff
Cpus_allowed_list:  0-31
Mems_allowed:   00000000,00000000,00000000,00000001
Mems_allowed_list:  0
voluntary_ctxt_switches:    123
nonvoluntary_ctxt_switches: 45

/proc/pid/maps — Memory Maps

$ cat /proc/self/maps
address           perms offset  dev   inode       pathname
00400000-0040b000 r-xp 00000000 08:01 1234567     /usr/bin/cat
0060a000-0060b000 r--p 0000a000 08:01 1234567     /usr/bin/cat
0060b000-0060c000 rw-p 0000b000 08:01 1234567     /usr/bin/cat
7f1234567000-7f1234589000 r-xp 00000000 08:01 2345678 /usr/lib/libc-2.31.so
7f1234589000-7f1234789000 ---p 00022000 08:01 2345678 /usr/lib/libc-2.31.so
7f1234789000-7f123478d000 r--p 00022000 08:01 2345678 /usr/lib/libc-2.31.so
7f123478d000-7f123478f000 rw-p 00026000 08:01 2345678 /usr/lib/libc-2.31.so
7f123478f000-7f1234795000 rw-p 00000000 00:00 0
7ffd12345000-7ffd12367000 rw-p 00000000 00:00 0          [stack]
7ffd12378000-7ffd1237b000 r--p 00000000 00:00 0          [vvar]
7ffd1237b000-7ffd1237c000 r-xp 00000000 00:00 0          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0  [vsyscall]

/proc/pid/fd/ — File Descriptors

$ ls -la /proc/self/fd
total 0
lrwx------ 1 user user 64 Jan 15 10:00 0 -> /dev/pts/0
lrwx------ 1 user user 64 Jan 15 10:00 1 -> /dev/pts/0
lrwx------ 1 user user 64 Jan 15 10:00 2 -> /dev/pts/0
lr-x------ 1 user user 64 Jan 15 10:00 3 -> /proc/12345/fd

# Count open files for a process
$ ls /proc/1234/fd | wc -l

# Find which process has a file open
$ sudo ls -la /proc/*/fd 2>/dev/null | grep deleted

/proc/pid/io — I/O Statistics

$ cat /proc/self/io
rchar: 3216       # Bytes read (including from cache)
wchar: 0          # Bytes written
syscr: 5          # Read syscalls
syscw: 0          # Write syscalls
read_bytes: 0     # Bytes actually read from storage
write_bytes: 0    # Bytes actually written to storage
cancelled_write_bytes: 0

/proc/pid/smaps — Detailed Memory Maps

$ cat /proc/self/smaps | head -30
00400000-0040b000 r-xp 00000000 08:01 1234567     /usr/bin/cat
Size:                 44 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                  44 kB
Pss:                  44 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:        44 kB
Private_Dirty:         0 kB
Referenced:           44 kB
Anonymous:             0 kB
LazyFree:              0 kB
AnonHugePages:         0 kB
ShmemPmdMapped:        0 kB
Shared_Hugetlb:        0 kB
Private_Hugetlb:       0 kB
Swap:                  0 kB
SwapPss:               0 kB
Locked:                0 kB
THPeligible:           0
ProtectionKey:         0
VmFlags: rd ex mr mw me dw

/proc/pid/ns/ — Namespaces

$ ls -la /proc/self/ns/
total 0
lrwxrwxrwx 1 user user 0 Jan 15 10:00 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 user user 0 Jan 15 10:00 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 user user 0 Jan 15 10:00 mnt -> 'mnt:[4026531841]'
lrwxrwxrwx 1 user user 0 Jan 15 10:00 net -> 'net:[4026531840]'
lrwxrwxrwx 1 user user 0 Jan 15 10:00 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 user user 0 Jan 15 10:00 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 user user 0 Jan 15 10:00 user -> 'user:[4026531837]'
lrwxrwxrwx 1 user user 0 Jan 15 10:00 uts -> 'uts:[4026531838]'

/proc/sys/* — Kernel Tunables

The /proc/sys/ hierarchy exposes kernel parameters that can be read and often written:

Virtual Memory (/proc/sys/vm/)

$ ls /proc/sys/vm/
block_dump           dirty_expire_centisecs  min_free_kbytes
compact_memory       dirty_ratio             mmap_min_addr
dirty_background_bytes  dirty_writeback_centisecs  nr_hugepages
dirty_background_ratio  drop_caches           nr_overcommit_hugepages
dirty_bytes          extfrag_threshold       oom_dump_tasks
dirty_expire_interval  hugepages_tlb        oom_kill_allocating_task
                    _backend               overcommit_memory
                    ms                     overcommit_ratio
                     swappiness            vfs_cache_pressure
                     watermark_boost_factor
                     watermark_scale_factor

Key VM Tunables

# Swappiness (0-200): How aggressively to swap
$ cat /proc/sys/vm/swappiness
60
$ echo 10 > /proc/sys/vm/swappiness

# Dirty ratio: % of memory that can be dirty before sync writes
$ cat /proc/sys/vm/dirty_ratio
20

# Dirty background ratio: % of memory for background writeback
$ cat /proc/sys/vm/dirty_background_ratio
10

# VFS cache pressure: how aggressively to reclaim dentry/inode cache
$ cat /proc/sys/vm/vfs_cache_pressure
100

# Overcommit memory policy
$ cat /proc/sys/vm/overcommit_memory
0  # 0=heuristic, 1=always, 2=strict

# Drop caches (1=pagecache, 2=dentries/inodes, 3=both)
$ echo 3 > /proc/sys/vm/drop_caches

# Minimum free memory
$ cat /proc/sys/vm/min_free_kbytes
67584

Kernel Parameters (/proc/sys/kernel/)

# Hostname
$ cat /proc/sys/kernel/hostname
myserver

# Maximum PID value
$ cat /proc/sys/kernel/pid_max
4194304

# SysRq key (emergency operations)
$ cat /proc/sys/kernel/sysrq
176  # bitmask of allowed operations

# Enable SysRq
$ echo 1 > /proc/sys/kernel/sysrq

# Panic timeout (auto-reboot after panic)
$ echo 60 > /proc/sys/kernel/panic

# Shared memory max
$ cat /proc/sys/kernel/shmmax
18446744073692774399

# Message queue limits
$ cat /proc/sys/kernel/msgmnb
16384
$ cat /proc/sys/kernel/msgmax
8192

Network Parameters (/proc/sys/net/)

$ ls /proc/sys/net/
core/  ipv4/  ipv6/  netfilter/  unix/

# TCP/IP tuning
$ cat /proc/sys/net/ipv4/tcp_fin_timeout
60

$ cat /proc/sys/net/ipv4/tcp_max_syn_backlog
1024

$ cat /proc/sys/net/core/somaxconn
4096

# Enable IP forwarding
$ echo 1 > /proc/sys/net/ipv4/ip_forward

# Increase connection tracking table size
$ echo 262144 > /proc/sys/net/netfilter/nf_conntrack_max

# TCP buffer sizes
$ cat /proc/sys/net/ipv4/tcp_rmem
4096    131072  6291456
$ cat /proc/sys/net/ipv4/tcp_wmem
4096    16384   4194304

File System Parameters (/proc/sys/fs/)

$ ls /proc/sys/fs/
aio-max-nr       file-max         inotify      pipe-user-pages-hard
aio-nr           file-nr          lease-break-time  pipe-user-pages-max
binfmt_misc/     inode-nr         leases-enable     protected_fifos
dentry-state     inode-state      mount-max         protected_hardlinks
epoll            mqueue/          nr_open           protected_regular
                 overflowgid      protected_symlinks
                 overflowuid      suid_dumpable

# Maximum open files system-wide
$ cat /proc/sys/fs/file-max
9223372036854775807

# Current open files
$ cat /proc/sys/fs/file-nr
12345   0   9223372036854775807

# Inode cache stats
$ cat /proc/sys/fs/inode-nr
24832   134

# Maximum inotify instances and watches
$ cat /proc/sys/fs/inotify/max_user_instances
128
$ cat /proc/sys/fs/inotify/max_user_watches
65536

System Information Files

/proc/cpuinfo

$ cat /proc/cpuinfo
processor   : 0
vendor_id   : GenuineIntel
cpu family  : 6
model       : 142
model name  : Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
stepping    : 10
microcode   : 0xca
cpu MHz     : 1800.000
cache size  : 8192 KB
physical id : 0
siblings    : 8
core id     : 0
cpu cores   : 4
apicid      : 0
initial apicid  : 0
fpu     : yes
fpu_exception   : yes
cpuid level : 22
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca
              cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm
              pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon
              pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf
              pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg
              fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt
              tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm
              3dnowprefetch cpuid_fault invpcid_single ssbd ibrs ibpb stibp
              tpr_shadow vnmi flexpriority ept vpid ept_ad fsgsbase tsc_adjust
              bmi1 avx2 smep bmi2 erms invpcid xsaveopt dtherm ida arat pln
              pts hwp hwp_notify hwp_act_window hwp_epp md_clear flush_l1d
bogomips    : 3984.00
clflush size    : 64
cache_alignment : 64
address sizes   : 39 bits physical, 48 bits virtual
power management:

/proc/meminfo

$ cat /proc/meminfo
MemTotal:       16384000 kB
MemFree:         8192000 kB
MemAvailable:   12288000 kB
Buffers:          512000 kB
Cached:          3072000 kB
SwapCached:            0 kB
Active:          4096000 kB
Inactive:        2048000 kB
Active(anon):    1024000 kB
Inactive(anon):   512000 kB
Active(file):    3072000 kB
Inactive(file):  1536000 kB
Unevictable:          0 kB
Mlocked:              0 kB
SwapTotal:       2097152 kB
SwapFree:        2097152 kB
Zswap:                 0 kB
Zswapped:              0 kB
Dirty:             12345 kB
Writeback:             0 kB
AnonPages:       1536000 kB
Mapped:           768000 kB
Shmem:            256000 kB
KReclaimable:    1024000 kB
Slab:             512000 kB
SReclaimable:     384000 kB
SUnreclaim:       128000 kB
KernelStack:       16384 kB
PageTables:        32768 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:    10289152 kB
Committed_AS:    6144000 kB
VmallocTotal:   34359738367 kB
VmallocUsed:       65536 kB
VmallocChunk:          0 kB
Percpu:             4096 kB
HardwareCorrupted:     0 kB
AnonHugePages:         0 kB
ShmemHugePages:        0 kB
ShmemPmdMapped:        0 kB
FileHugePages:         0 kB
FilePmdMapped:         0 kB
CmaTotal:              0 kB
CmaFree:               0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:               0 kB
DirectMap4k:      524288 kB
DirectMap2M:    16252928 kB
DirectMap1G:     1048576 kB

/proc/mounts and /proc/self/mountinfo

$ cat /proc/mounts
sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
devtmpfs /dev devtmpfs rw,nosuid,size=8192k,nr_inodes=2048000 0 0
/dev/sda1 / ext4 rw,relatime,errors=remount-ro 0 0

# More detailed mount info
$ cat /proc/self/mountinfo | head -5
22 1 0:21 / /sys rw,nosuid,nodev,noexec,relatime shared:7 - sysfs sysfs rw
23 1 0:22 / /proc rw,nosuid,nodev,noexec,relatime shared:12 - proc proc rw

Creating proc Entries (Kernel Module)

Kernel modules can create their own entries under /proc:

#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>

static int my_proc_show(struct seq_file *m, void *v)
{
    seq_printf(m, "Hello from my proc entry!\n");
    seq_printf(m, "Current jiffies: %lu\n", jiffies);
    return 0;
}

static int my_proc_open(struct inode *inode, struct file *file)
{
    return single_open(file, my_proc_show, NULL);
}

static ssize_t my_proc_write(struct file *file, const char __user *buf,
                              size_t count, loff_t *ppos)
{
    char kbuf[64];
    if (count > sizeof(kbuf) - 1)
        return -EINVAL;
    if (copy_from_user(kbuf, buf, count))
        return -EFAULT;
    kbuf[count] = '\0';
    pr_info("proc write: %s\n", kbuf);
    return count;
}

static const struct proc_ops my_proc_ops = {
    .proc_open    = my_proc_open,
    .proc_read    = seq_read,
    .proc_write   = my_proc_write,
    .proc_lseek   = seq_lseek,
    .proc_release = single_release,
};

static struct proc_dir_entry *my_entry;

static int __init my_init(void)
{
    /* Create /proc/my_entry */
    my_entry = proc_create("my_entry", 0666, NULL, &my_proc_ops);
    if (!my_entry)
        return -ENOMEM;

    /* Create /proc/mydir/my_entry (subdirectory) */
    proc_mkdir("mydir", NULL);
    proc_create("my_entry2", 0444, NULL, &my_proc_ops);

    return 0;
}

static void __exit my_exit(void)
{
    proc_remove(my_entry);
    remove_proc_entry("mydir", NULL);
}

module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");

seq_file Interface

For files that produce more than one page of output, use the seq_file interface:

static void *my_seq_start(struct seq_file *m, loff_t *pos)
{
    if (*pos >= MAX_ITEMS)
        return NULL;
    return pos;
}

static void *my_seq_next(struct seq_file *m, void *v, loff_t *pos)
{
    (*pos)++;
    if (*pos >= MAX_ITEMS)
        return NULL;
    return pos;
}

static void my_seq_stop(struct seq_file *m, void *v)
{
    /* Nothing to do */
}

static int my_seq_show(struct seq_file *m, void *v)
{
    loff_t *pos = v;
    seq_printf(m, "Item %lld\n", *pos);
    return 0;
}

static const struct seq_operations my_seq_ops = {
    .start = my_seq_start,
    .next  = my_seq_next,
    .stop  = my_seq_stop,
    .show  = my_seq_show,
};

procfs Security

hidepid Mount Option

# Hide other users' processes
$ sudo mount -o remount,hidepid=2 /proc

# Users can only see their own processes
$ ps aux
# Only shows current user's processes

Proc File Permissions

/* Restrict access to proc entries */
proc_create("sensitive_data", 0600, parent_dir, &my_ops);

/* Only root-readable */
proc_create("root_only", 0400, parent_dir, &my_ops);

Yama LSM and ptrace_scope

# Control process tracing permissions
$ cat /proc/sys/kernel/yama/ptrace_scope
0  # Classic: any process can trace any process with same UID
1  # Restricted: only parent can trace child
2  # Admin-only: only root can trace
3  # No tracing at all

Practical Examples

System Monitoring

# CPU usage per core (from /proc/stat)
$ cat /proc/stat | head -5
cpu  12345 0 67890 12345678 1234 0 567 0 0 0
cpu0 3086 0 16972 3086419 310 0 142 0 0 0
cpu1 3086 0 16972 3086419 310 0 142 0 0 0
cpu2 3086 0 16972 3086419 310 0 142 0 0 0
cpu3 3086 0 16972 3086419 310 0 142 0 0 0

# Network statistics
$ cat /proc/net/dev
Inter-|   Receive                                                |  Transmit
 face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo frame compressed
    lo: 1234567  12345    0    0    0     0          0         0  1234567  12345    0    0    0     0       0
  eth0: 9876543  98765    0    0    0     0          0         0  5678901  56789    0    0    0     0       0

# Load average
$ cat /proc/loadavg
0.52 0.48 0.45 1/456 12345

# Uptime
$ cat /proc/uptime
123456.78 234567.89

Process Inspection

# Get process command line
$ cat /proc/1/cmdline | tr '\0' ' '
/sbin/init splash

# Get environment variables
$ cat /proc/1/environ | tr '\0' '\n'

# Get process limits
$ cat /proc/1/limits
Limit                     Soft Limit           Hard Limit           Units
Max cpu time              unlimited            unlimited            seconds
Max file size             unlimited            unlimited            bytes
Max data size             unlimited            unlimited            bytes
Max stack size            8388608              unlimited            bytes
Max core file size        0                    unlimited            bytes
Max resident set          unlimited            unlimited            bytes
Max processes             123456               123456               processes
Max open files            1024                 1048576              files
Max locked memory         67108864             67108864             bytes
Max address space         unlimited            unlimited            bytes
Max file locks            unlimited            unlimited            locks
Max pending signals       123456               123456               signals
Max msgqueue size         819200               819200               bytes
Max nice priority         0                    0
Max realtime priority     0                    0
Max realtime timeout      unlimited            unlimited            us

Sysctl Compatibility

The sysctl command reads from /proc/sys/:

# Read a parameter
$ sysctl vm.swappiness
vm.swappiness = 60

# Set a parameter
$ sudo sysctl -w vm.swappiness=10

# Read all parameters
$ sysctl -a | wc -l
1234

# Persist parameters in /etc/sysctl.conf
$ echo "vm.swappiness = 10" | sudo tee -a /etc/sysctl.conf
$ sudo sysctl -p

Performance Considerations

Reading /proc files has overhead because content is generated dynamically:

# Benchmark: reading /proc files
$ time for i in $(seq 1 10000); do cat /proc/cpuinfo > /dev/null; done
real    0m5.123s
user    0m1.234s
sys     0m3.889s

# /proc is much slower than regular files for repeated reads
$ time for i in $(seq 1 10000); do cat /tmp/regular_file > /dev/null; done
real    0m0.567s

Mitigations

  1. Cache results: Don’t read /proc files in tight loops
  2. Use netlink: For network statistics, netlink sockets are more efficient
  3. Use /proc/pid/fdinfo: For per-fd info, more targeted than reading whole status files
  4. Poll selectively: Use poll()/select() on proc files that support it

Further Reading

  • VFS — The virtual filesystem layer that procfs sits on
  • sysfs — The /sys filesystem (device model)
  • Inode — procfs creates dynamic inodes
  • Dentry — procfs dentries are created dynamically