Chapter 5 — /sys: The Kernel’s Live Hardware Map

What Is sysfs?

/sys is the sysfs filesystem — a virtual filesystem that the kernel uses to export its internal data structures to userspace. Unlike /proc (which is process-oriented), sysfs is hardware-oriented. It reflects the kernel’s internal device model: buses, devices, drivers, and their interconnections.

Every kernel object with a kobject (the fundamental kernel reference-counted object) is potentially visible in /sys. This includes every device on every bus, every driver, every kernel subsystem.

ls /sys/
# block  bus  class  dev  devices  firmware  fs  hypervisor  kernel  module  power

The Top-Level Structure

Directory What It Contains
/sys/bus/ All bus types (pci, usb, i2c, spi, platform…)
/sys/class/ Devices grouped by class (net, block, input, thermal…)
/sys/devices/ The actual device tree (authoritative, all devices here)
/sys/block/ Block devices (symlinks into /sys/devices/)
/sys/module/ Loaded kernel modules and their parameters
/sys/kernel/ Kernel tunables (mm, debug, etc.)
/sys/power/ Power management controls
/sys/fs/ Filesystem-specific parameters (cgroup, ext4…)

/sys/class/ and /sys/bus/ contain symlinks into /sys/devices/. The actual data lives in /sys/devices/.

The device tree mirrors the hardware topology:

/sys/devices/
├── pci0000:00/          ← PCI domain 0, root bus
│   ├── 0000:00:02.0/    ← PCI device (GPU)
│   │   ├── driver/      → symlink to /sys/bus/pci/drivers/i915
│   │   ├── vendor       ← "0x8086"
│   │   ├── device       ← "0x591b"
│   │   ├── class        ← "0x030000"
│   │   ├── enable       ← "1" (device is enabled)
│   │   ├── irq          ← "127"
│   │   └── power/
│   └── 0000:00:1f.3/    ← another PCI device
│
├── platform/            ← Platform devices (CPU, ACPI, etc.)
│   ├── coretemp.0/
│   └── ...
│
└── virtual/             ← Virtual devices (loop, null, etc.)

Reading Sysfs Attributes

Sysfs attributes are files that contain a single value. Reading them is just reading a file:

def read_sysfs(path):
    with open(path, "r") as f:
        return f.read().strip()

# Read CPU temperature (in millidegrees Celsius)
temp_raw = read_sysfs("/sys/class/thermal/thermal_zone0/temp")
print(f"CPU temp: {int(temp_raw) / 1000:.1f}°C")

# Read network interface speed
speed = read_sysfs("/sys/class/net/eth0/speed")
print(f"Link speed: {speed} Mbit/s")

Some attributes are writable — writing to them changes kernel behavior:

# Turn on a LED (if you have one)
with open("/sys/class/leds/input3::capslock/brightness", "w") as f:
    f.write("1")

/sys/class: Devices Grouped by Function

/sys/class/ groups devices by what they do, regardless of bus. This is the most useful view for most practical tasks:

ls /sys/class/
# backlight  block  bluetooth  gpio  hwmon  input  leds  net
# power_supply  thermal  tty  usbmisc  v4l2  ...

Useful Classes

Network interfaces:

ls /sys/class/net/
# eth0  lo  wlan0  docker0

cat /sys/class/net/eth0/operstate    # "up" or "down"
cat /sys/class/net/eth0/speed        # link speed in Mbit/s
cat /sys/class/net/eth0/address      # MAC address
cat /sys/class/net/eth0/statistics/rx_bytes  # bytes received

Thermal zones (temperatures):

ls /sys/class/thermal/
# cooling_device0  thermal_zone0  thermal_zone1  ...

cat /sys/class/thermal/thermal_zone0/type   # "x86_pkg_temp"
cat /sys/class/thermal/thermal_zone0/temp   # "45000" = 45°C

Power supply (battery):

ls /sys/class/power_supply/
# AC  BAT0

cat /sys/class/power_supply/BAT0/capacity       # "87" (percent)
cat /sys/class/power_supply/BAT0/status         # "Discharging"
cat /sys/class/power_supply/BAT0/energy_now     # in microwatt-hours
cat /sys/class/power_supply/BAT0/energy_full    # design capacity

Block devices:

cat /sys/class/block/sda/size          # in 512-byte sectors
cat /sys/class/block/sda/queue/rotational  # 0=SSD, 1=HDD
cat /sys/class/block/sda/queue/scheduler  # I/O scheduler

Input devices:

ls /sys/class/input/
# event0  event1  input0  mouse0  ...

cat /sys/class/input/input0/name       # "Power Button"
cat /sys/class/input/input1/name       # "Lid Switch"

Hardware monitor (fan speed, voltages):

ls /sys/class/hwmon/
# hwmon0  hwmon1  ...

cat /sys/class/hwmon/hwmon0/name       # "coretemp"
cat /sys/class/hwmon/hwmon0/temp1_input  # CPU temp in millidegrees
cat /sys/class/hwmon/hwmon0/fan1_input  # fan RPM (if available)

/sys/bus: The Bus View

ls /sys/bus/
# acpi  cpu  i2c  pci  platform  spi  usb  ...

ls /sys/bus/pci/devices/     # all PCI devices (symlinks)
ls /sys/bus/pci/drivers/     # all PCI drivers

# Find which PCI devices use the i915 driver
ls /sys/bus/pci/drivers/i915/
# 0000:00:02.0  bind  module  new_id  remove_id  uevent  unbind

You can bind and unbind drivers at runtime:

# Unbind a device from its driver
echo "0000:00:02.0" | sudo tee /sys/bus/pci/drivers/i915/unbind

# Bind it to a different driver
echo "0000:00:02.0" | sudo tee /sys/bus/pci/drivers/vfio-pci/bind

This is how GPU passthrough to virtual machines works.

/sys/module: Module Parameters

ls /sys/module/
# e1000e  i915  usbcore  ...

ls /sys/module/usbcore/parameters/
# autosuspend  autosuspend_delay_ms  use_both_schemes  usbfs_memory_mb

cat /sys/module/usbcore/parameters/usbfs_memory_mb
# 16

You can change module parameters at runtime (if the attribute is writable):

echo 64 | sudo tee /sys/module/usbcore/parameters/usbfs_memory_mb

Practical Python: Enumerating System Hardware

import os
from pathlib import Path

def list_pci_devices():
    base = Path("/sys/bus/pci/devices")
    for dev in sorted(base.iterdir()):
        vendor = (dev / "vendor").read_text().strip()
        device_id = (dev / "device").read_text().strip()
        cls = (dev / "class").read_text().strip()
        print(f"{dev.name}  vendor={vendor}  device={device_id}  class={cls}")

list_pci_devices()
def battery_status():
    bat = Path("/sys/class/power_supply/BAT0")
    if not bat.exists():
        return "No battery"
    capacity = (bat / "capacity").read_text().strip()
    status = (bat / "status").read_text().strip()
    return f"{capacity}% ({status})"

print(battery_status())

Sysfs makes heavy use of symlinks to represent relationships:

# A device's driver
readlink /sys/bus/pci/devices/0000:00:02.0/driver
# ../../../../bus/pci/drivers/i915

# A driver's devices
ls /sys/bus/pci/drivers/i915/
# 0000:00:02.0  (symlink back to the device)

# A block device's parent (the physical disk)
readlink /sys/class/block/sda1/..
# → /sys/devices/pci.../ata1/host0/.../sda

Following these symlinks you can reconstruct the entire hardware graph programmatically.


Previous: Chapter 4 — /dev

Next: Chapter 6 — /proc: Process and Hardware Introspection

Back to Table of Contents