/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
| 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.)
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/ 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 ...
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)
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.
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
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