Chapter 6 — /proc: Process and Hardware Introspection

What Is procfs?

/proc is the proc filesystem — one of Linux’s oldest virtual filesystems. It was originally designed to expose process information (hence the name), but has grown to expose a wide range of kernel and hardware state.

Unlike sysfs (/sys), which follows a strict object model, /proc is more of a grab-bag of useful information. Some entries are per-process, some are system-wide hardware info, and some are kernel tunables.

ls /proc/
# 1  2  3  ...  (numbered directories = process PIDs)
# buddyinfo  cmdline  cpuinfo  devices  diskstats  dma
# interrupts  iomem  ioports  meminfo  modules  mounts
# net  partitions  slabinfo  stat  swaps  sys  uptime  version

Per-Process Information

Each running process has a numbered directory:

ls /proc/1234/     # for PID 1234
# cmdline  cwd  environ  exe  fd  maps  mem  net
# smaps  stat  status  task  ...

Key files per process:

cat /proc/$$/cmdline | tr '\0' ' '    # command line (null-separated)
cat /proc/$$/status                   # human-readable process info
cat /proc/$$/maps                     # memory mappings
ls -la /proc/$$/fd/                   # open file descriptors
readlink /proc/$$/exe                 # path to executable
cat /proc/$$/environ | tr '\0' '\n'   # environment variables

From Python:

import os

pid = os.getpid()
with open(f"/proc/{pid}/status") as f:
    for line in f:
        if line.startswith("VmRSS"):
            print(line.strip())  # resident memory usage

Hardware Information Files

/proc/cpuinfo

Detailed information about each CPU core:

cat /proc/cpuinfo
processor   : 0
vendor_id   : GenuineIntel
cpu family  : 6
model name  : Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
cpu MHz     : 800.000
cache size  : 8192 KB
physical id : 0
siblings    : 8
core id     : 0
flags       : fpu vme de pse tsc msr pae mce cx8 apic ...
def cpu_info():
    cores = []
    current = {}
    with open("/proc/cpuinfo") as f:
        for line in f:
            line = line.strip()
            if line == "":
                if current:
                    cores.append(current)
                current = {}
            elif ":" in line:
                key, _, val = line.partition(":")
                current[key.strip()] = val.strip()
    return cores

cores = cpu_info()
print(f"CPU: {cores[0]['model name']}")
print(f"Cores: {len(cores)}")

/proc/meminfo

System memory statistics:

cat /proc/meminfo
MemTotal:       16237440 kB
MemFree:         2134528 kB
MemAvailable:    8452096 kB
Buffers:          512000 kB
Cached:          5120000 kB
SwapTotal:       8388604 kB
SwapFree:        8388604 kB
def mem_info():
    info = {}
    with open("/proc/meminfo") as f:
        for line in f:
            key, _, val = line.partition(":")
            info[key.strip()] = val.strip()
    total = int(info["MemTotal"].split()[0])
    avail = int(info["MemAvailable"].split()[0])
    print(f"Memory: {avail//1024} MB free / {total//1024} MB total")

mem_info()

/proc/interrupts

The interrupt table — shows which hardware is generating interrupts and how many:

cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3
  0:         18          0          0          0  IO-APIC   2-edge      timer
  1:          0          0          0       4213  IO-APIC   1-edge      i8042
 16:          0          0          0          0  IO-APIC  16-fasteoi   i801_smbus
 24:          0      12891          0          0  PCI-MSI 327680-edge   xhci_hcd
 25:          0          0     214760          0  PCI-MSI 360448-edge   i915
NMI:          4          4          4          4  Non-maskable interrupts
LOC:     543219     578436     521847     509124  Local timer interrupts

Each row is an interrupt vector. The numbers show how many times each CPU has handled that interrupt. i915 is the GPU driver, xhci_hcd is USB.

def interrupt_counts():
    with open("/proc/interrupts") as f:
        header = f.readline().split()  # CPU names
        for line in f:
            parts = line.split()
            if len(parts) < 2:
                continue
            irq = parts[0].rstrip(":")
            counts = parts[1:1+len(header)]
            name = " ".join(parts[1+len(header):]) if len(parts) > 1+len(header) else ""
            total = sum(int(c) for c in counts if c.isdigit())
            if total > 0:
                print(f"IRQ {irq:>4}: {total:>10}  {name}")

/proc/iomem

The physical memory map — shows how physical addresses are allocated between hardware:

cat /proc/iomem
00000000-00000fff : Reserved
00001000-0009e7ff : System RAM
0009e800-0009ffff : Reserved
000a0000-000bffff : PCI Bus 0000:00
000c0000-000c7fff : Video ROM
...
00100000-7ef0bfff : System RAM
  01000000-02006f42 : Kernel code
  02006f43-027cbbff : Kernel data
80000000-8fffffff : PCI Bus 0000:01
  80000000-8fffffff : 0000:01:00.0  ← GPU memory region
fd000000-fd3fffff : PCI Bus 0000:00
  fd000000-fd0fffff : 0000:00:02.0  ← Intel GPU registers

This is crucial for memory-mapped I/O — the physical addresses where hardware registers live.

/proc/ioports

I/O port space (x86 only) — the legacy port-mapped I/O space:

cat /proc/ioports
0000-0cf7 : PCI Bus 0000:00
  0000-001f : dma1
  0020-0021 : PIC1
  0040-0043 : timer0
  0060-0060 : keyboard
  0070-0071 : rtc_cmos
  00f0-00ff : fpu

These are I/O port addresses (different from memory addresses) that legacy hardware uses. Modern hardware mostly uses MMIO instead.

/proc/devices

Maps major device numbers to driver names (we saw this in Chapter 4):

cat /proc/devices

/proc/modules

Currently loaded kernel modules (same as lsmod output):

cat /proc/modules | head -5
# e1000e 282624 0 - Live 0xffffffffc08e0000

Format: name size instances_loaded deps state address

/proc/mounts and /proc/partitions

cat /proc/mounts      # all mounted filesystems
cat /proc/partitions  # all disk partitions

/proc/net/

Network statistics:

ls /proc/net/
# arp  dev  if_inet6  route  tcp  tcp6  udp  unix  ...

cat /proc/net/dev     # per-interface packet/byte counters
cat /proc/net/arp     # ARP table
cat /proc/net/route   # routing table

/proc/sys: Kernel Tunables

/proc/sys/ contains writable kernel parameters (same as sysctl):

ls /proc/sys/
# kernel  net  vm  fs  ...

cat /proc/sys/kernel/hostname
# mycomputer

# Change hostname at runtime (does not persist reboot)
echo "newname" | sudo tee /proc/sys/kernel/hostname

# Equivalent sysctl command
sudo sysctl -w kernel.hostname=newname

Key tunables:

/proc/sys/net/ipv4/ip_forward          # enable IP forwarding (router mode)
/proc/sys/vm/swappiness                # swap aggressiveness (default 60)
/proc/sys/kernel/dmesg_restrict        # who can read dmesg
/proc/sys/fs/file-max                  # max open file descriptors system-wide

/proc/stat and /proc/diskstats

These are used by tools like top, htop, and iostat:

cat /proc/stat         # CPU time breakdown (user, system, idle, iowait...)
cat /proc/diskstats    # disk I/O statistics per device

/proc/stat is what Python’s psutil library reads to compute CPU usage.


Previous: Chapter 5 — /sys

Next: Chapter 7 — Interrupts and DMA

Back to Table of Contents