Linux Lesson 28 of 47

The Kernel Up Close: Modules, /proc, /sys, sysctl, dmesg & Kernel Parameters

Every other lesson in this course eventually bottoms out here. When a process is killed and you don’t know why, when a NIC won’t come up, when a database begs you to raise a limit, when a disk starts throwing errors, when a driver misbehaves after a kernel upgrade — the answer lives in one of five places: a loaded module, a file under /proc, a file under /sys, a sysctl knob, or a line in the dmesg ring buffer. Master those five surfaces and the kernel stops being a mysterious monolith you pray to and becomes a system you can read, tune, and repair.

This is an expert lesson, but it is built from first principles. We will not memorise commands; we will understand the interfaces those commands drive, because the commands are just thin skins over a small number of kernel-provided windows. Once you see that sysctl net.ipv4.ip_forward is literally reading the file /proc/sys/net/ipv4/ip_forward, that lsmod is just formatting /proc/modules, and that dmesg is just draining a fixed-size in-memory buffer, the whole subject collapses into something you can hold in your head.

Type every command. Where you see expected output, run it on your own box and compare. The kernel is the one part of Linux where guessing is genuinely dangerous, so we earn every claim with real output.


Why this matters

The kernel is the program that owns the hardware. It is the only code allowed to talk to the CPU’s privileged instructions, the memory controller, the disks, and the network cards. Everything else — your shell, your web server, systemd, even init — is userspace: unprivileged programs that must ask the kernel for everything through a narrow, guarded doorway. A beginner’s mental model of “Linux” is usually a pile of commands. The professional’s model is a boundary: a thin, well-defined membrane between userspace and the kernel, with a handful of ways to cross it or peer through it.

Here is where a beginner first hits this wall. You run an application and it dies with “Killed” and no stack trace — the log is empty, but dmesg has a one-line obituary written by the kernel’s OOM killer. You plug in a USB adapter and nothing happens — dmesg shows the kernel probing it and failing to find a driver. Your Elasticsearch node refuses to start until vm.max_map_count is raised — a sysctl. Your monitoring says a disk is “rotational” and picks the wrong I/O scheduler — a file under /sys/block. Your VPN needs net.ipv4.ip_forward=1 to route packets — another sysctl. None of these are application problems. They are all conversations with the kernel, and you cannot have them if you don’t know where the kernel keeps its state.

The mental model to build is this: the kernel exposes itself to userspace through exactly a few interfaces, and every diagnostic and tuning tool you know is a client of one of them. Syscalls are the active API (programs do things). /proc and /sys are pseudo-filesystems the kernel synthesises on the fly so you can read its state as if it were files. sysctl writes to a slice of /proc to tune it. dmesg reads the kernel’s own log. Loadable modules are how the kernel grows new capabilities — drivers, filesystems, netfilter hooks — without a reboot. Get this map straight and you will always know where to look.

This lesson is the “internals” capstone of the fundamentals track. It pairs naturally with what you already know about the boot process, GRUB, and initramfs (where the kernel and its early modules first load), processes and signals (whose truth lives in /proc/<pid>/), and performance tuning (which is mostly sysctl and /sys knobs applied with judgement).


1. The kernel and its interfaces to userspace

Start with the boundary. A CPU runs in one of (at least) two privilege levels. On x86 these are “rings”: ring 0 is kernel mode, where code may execute privileged instructions and touch any memory; ring 3 is user mode, where it cannot. Your programs run in ring 3. They cannot open a file, send a packet, or allocate memory from the OS directly — those all require privileged operations. Instead they make a system call (syscall): they put a number and some arguments in registers and execute a special trap instruction (syscall on x86-64) that hands control to a fixed kernel entry point, in ring 0, which does the work and returns. This is the only true API of the kernel. Everything else is a convenience built on top.

You almost never write raw syscalls; the C library (glibc on most distros, musl on Alpine) wraps them in familiar functions. fopen() calls open(2); printf() eventually calls write(2). You can watch the real syscalls a program makes with strace:

# Trace every syscall the command 'id' makes (truncated)
strace -f id 2>&1 | head -12
execve("/usr/bin/id", ["id"], 0x7ffe... /* 30 vars */) = 0
brk(NULL)                               = 0x55e3...
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0"..., 832) = 832
...
write(1, "uid=1000(vinod) gid=1000(vinod) ", 32) = 32

Every line is a crossing of the userspace↔kernel boundary. That is what your programs do all day. But syscalls are an active interface — you call them, they act, they return. For reading and tuning the kernel’s state, Linux offers something more ergonomic: it makes the state look like files. This is the Unix “everything is a file” philosophy taken to its logical conclusion, and it gives us the four windows you will use constantly.

The interfaces, at a glance. The table below is the map for the entire lesson. Everything after this is detail on one of these rows.

Interface What it is Mounted / reached at You use it to Backed by
syscalls The kernel’s true API (trap into ring 0) CPU trap instruction, via glibc Do things: open, read, write, mmap, ioctl, socket The kernel’s syscall table
/dev Device special files (char + block) /dev (devtmpfs) Talk to a specific device via open/read/ioctl Device drivers
/proc Per-process + system info as files /proc (procfs) Read process and system state CONFIG_PROC_FS, synthesised on read
/sys The device + kernel-object model /sys (sysfs) Inspect and set device/driver attributes CONFIG_SYSFS, the driver core
sysctl Named kernel tunables /proc/sys/ (a slice of procfs) Read and write runtime knobs (vm.*, net.*, …) procfs, sysctl front-end
netlink A socket-based control channel AF_NETLINK sockets Config networking (ip, ss), get udev events Kernel netlink subsystem
dmesg The kernel’s log (ring buffer) /dev/kmsg, dmesg Read what the kernel is saying printk, a fixed-size buffer

Two of these deserve an early word because beginners trip on them. First, /proc and /sys are not real files. Nothing is stored on a disk. When you cat /proc/meminfo, the kernel generates that text at the instant of the read, from live counters. That is why their sizes are usually reported as 0 and why you cannot meaningfully cp the whole tree. They are pseudo-filesystems: a filesystem API bolted onto in-memory kernel data. Second, netlink is the modern control plane for networking and device events. You will not poke it by hand, but know that when ip addr shows you an address or udevadm monitor shows a device appearing, that data arrived over a netlink socket — another distinct userspace↔kernel interface beyond the file-like ones.

You can see the pseudo-filesystems that implement all this, mounted right now:

# Show the virtual filesystems the kernel projects into your namespace
mount | grep -E '^(proc|sysfs|devtmpfs|tmpfs|cgroup2|debugfs) '
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
devtmpfs on /dev type devtmpfs (rw,nosuid,relatime,size=8123456k,nr_inodes=...)
tmpfs on /run type tmpfs (rw,nosuid,nodev,...)
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
Pseudo-FS Type Mount point Holds
procfs proc /proc Per-PID dirs + system files + /proc/sys (sysctl tree)
sysfs sysfs /sys Device/driver/bus/class object model + module params
devtmpfs devtmpfs /dev Device nodes the kernel auto-creates on boot
cgroup2 cgroup2 /sys/fs/cgroup Resource-control hierarchy (CPU, memory limits)
debugfs debugfs /sys/kernel/debug Ad-hoc kernel debug knobs (root only)
securityfs securityfs /sys/kernel/security LSM (SELinux/AppArmor/lockdown) state
tracefs tracefs /sys/kernel/tracing ftrace / event tracing control

The diagram below is the whole picture. On the left is userspace — your programs and you as root. In the centre is the one true doorway (syscalls, plus the /dev nodes reached through them) leading into the kernel and its loadable modules. On the right the kernel projects its state back out as /proc and /sys, accepts live tuning through sysctl, and — running off the bottom of the core — writes everything it wants to tell you into the dmesg ring buffer. Trace the badges in order and you have the reading path for every problem in this lesson.

Diagram of the Linux kernel and its userspace interfaces: programs and root call in through syscalls and /dev, the kernel core loads modules via modprobe, exposes its state as the /proc and /sys pseudo-filesystems, is tuned live through sysctl persisted in sysctl.d, and speaks back to userspace through the dmesg ring buffer where OOM kills and oops messages appear

Keep this map in mind: the rest of the lesson walks the windows one at a time, starting with the kernel’s own identity.


2. Which kernel are you running? Version, config, and command line

Before you tune or troubleshoot a kernel you must be able to identify it precisely, because module compatibility, available features, and known bugs are all version-specific. A module built for 6.8.0-31 will refuse to load on 6.8.0-35. A tunable that exists on a 6.x kernel may be absent on the 5.14 kernel RHEL 9 ships. So the first question is always: exactly which kernel, built with what config, booted with what parameters?

uname: the kernel’s name tag

uname -a
Linux web01 6.8.0-31-generic #31-Ubuntu SMP PREEMPT_DYNAMIC Sat ... x86_64 x86_64 x86_64 GNU/Linux

That single line packs several fields. The one you will type a hundred times is uname -r, the kernel release, because it is the directory name under /lib/modules/ where this kernel’s modules live — you will splice $(uname -r) into paths constantly.

Flag Prints Example
-r Kernel release (use in module paths) 6.8.0-31-generic
-v Kernel version (build string + date) #31-Ubuntu SMP PREEMPT_DYNAMIC ...
-m Machine hardware (architecture) x86_64
-o Operating system GNU/Linux
-n Node (hostname) web01
-p Processor type (often unknown) x86_64
-a All of the above (full line)

Decode a release string like 6.8.0-31-generic: 6 = major, 8 = minor (the “6.8 mainline series”), 0 = patch, -31 = the distro’s build/ABI number (Ubuntu’s 31st build of 6.8), -generic = the flavour (vs -lowlatency, -aws, -rt). On RHEL 9 you would instead see 5.14.0-427.el9.x86_64, where .el9 marks it as Red Hat’s heavily back-ported “Enterprise Linux 9” kernel — nominally 5.14 but carrying thousands of patches from far newer kernels. This is the single most important thing to understand about kernels in the real world:

Distro kernels vs mainline vs LTS

The kernel your distro ships is not the vanilla kernel from kernel.org. Distros take a base version and back-port security fixes, drivers, and features onto it for years while keeping the version number frozen for ABI stability. “Is RHEL 9 really on the ancient 5.14?” — no; it is 5.14 in name only, patched to behave like something far newer. Meanwhile Ubuntu tracks closer to mainline and rolls the base version forward across point releases.

Kernel line What it is Who uses it Support window
mainline Torvalds’ latest rc/release on kernel.org Kernel developers, bleeding-edge Weeks (until next release)
stable Mainline + fixes for a short spell Rolling distros (Arch, Fedora) ~Until next stable
LTS A stable line maintained for years Debian, Ubuntu LTS base, Android ~2–6 years
distro / enterprise An LTS/stable base heavily back-ported, version frozen RHEL, SLES, Ubuntu LTS 5–10+ years
-rt (PREEMPT_RT) Real-time preemption patchset Industrial, audio, robotics Tracks its base
hardened Extra security hardening (grsec-style, Alpine) Security-focused distros Tracks its base

The practical rule: on a server, run the kernel your distro ships and let apt/dnf update it. You inherit tested drivers, signed modules for Secure Boot, and a security team back-porting CVE fixes for you. Build your own only for a genuine reason (unusual hardware, kernel development, a feature your distro’s config omits) — and we will see at the end of this section why that is rarely worth it.

What was this kernel built with? The config

A kernel is compiled from thousands of options — CONFIG_* symbols set to y (built in), m (built as a loadable module), or unset. To know whether a feature or driver is even possible on your running kernel, read its config. There are two places it may live:

# 1) Almost always present: the config saved beside the kernel in /boot
grep -c '=y' /boot/config-$(uname -r)      # count built-in options
grep CONFIG_SYSFS /boot/config-$(uname -r) # is sysfs compiled in?
CONFIG_SYSFS=y
# 2) Sometimes exposed by the running kernel itself (needs CONFIG_IKCONFIG_PROC)
zcat /proc/config.gz 2>/dev/null | grep CONFIG_TRANSPARENT_HUGEPAGE=
Source of config Availability How to read it
/boot/config-$(uname -r) Ships on Debian/Ubuntu and RHEL/Fedora grep CONFIG_X /boot/config-$(uname -r)
/proc/config.gz Only if built with CONFIG_IKCONFIG_PROC=y (Arch: yes; Debian/RHEL: usually no) zcat /proc/config.gz | grep ...
/lib/modules/$(uname -r)/build/.config If kernel-headers/-devel installed grep ... .../.config
Config value Meaning Consequence
CONFIG_XYZ=y Feature/driver built into the kernel image Always present; cannot be unloaded
CONFIG_XYZ=m Built as a loadable module Present as an .ko; load/unload with modprobe
# CONFIG_XYZ is not set Not compiled at all Feature unavailable without rebuilding the kernel

This y/m/unset distinction is the bridge to the next section: a feature marked m in the config is exactly what shows up as a loadable module you can manage at runtime.

The kernel command line

When the bootloader launched this kernel it passed a command line — a space-separated list of parameters that tune the kernel before userspace even exists. Read the one in force right now:

cat /proc/cmdline
BOOT_IMAGE=/vmlinuz-6.8.0-31-generic root=UUID=1e0e... ro quiet splash mitigations=off
Common parameter Effect
root=UUID=… Which device to mount as /
ro / rw Mount root read-only (then remount rw) or read-write
quiet Suppress most boot messages
nomodeset Disable kernel mode-setting (rescue a black-screen GPU)
single / systemd.unit=rescue.target Boot to a rescue shell
transparent_hugepage=never Disable THP globally (common for databases)
mitigations=off Disable CPU vulnerability mitigations (⚠️ security trade-off)
intel_iommu=on Enable the IOMMU (for VFIO/passthrough)
init=/bin/bash Skip systemd, land in a raw shell (password recovery)

To make a change permanent you do not edit /proc/cmdline (it is read-only truth). You edit the bootloader config, as covered in the boot-process lesson: on Debian/Ubuntu set GRUB_CMDLINE_LINUX in /etc/default/grub then sudo update-grub; on RHEL/Fedora use sudo grubby --update-kernel=ALL --args="transparent_hugepage=never" or edit and run sudo grub2-mkconfig -o /boot/grub2/grub.cfg. To try a parameter once without committing, press e at the GRUB menu and edit the linux line — it applies for that one boot only.

A taste of building a kernel (and why you usually don’t)

You can compile your own kernel, and it is educational to do once. The rough shape on Debian/Ubuntu:

# ⚠️ Illustrative only — do not run on a machine you care about
sudo apt build-dep linux            # pull build toolchain
apt source linux-image-$(uname -r)  # fetch the distro's source
cd linux-*/
make localmodconfig                 # start from a config matching THIS machine's loaded modules
make -j"$(nproc)"                    # compile (minutes to hours)
sudo make modules_install install    # install modules + image, update GRUB

Now the reasons you almost never should on a server: you lose the distro’s continuous CVE back-porting; every kernel update becomes a manual rebuild; with Secure Boot on you must sign the image and modules yourself; and any out-of-tree module (GPU, VPN) must be rebuilt to match. The distro’s kernel team does all of this for you, tested, for free. Build your own for learning, for exotic hardware, or for a feature your distro genuinely omits — otherwise dnf upgrade / apt full-upgrade is the professional answer. The exception that is common is a single out-of-tree module (an NVIDIA driver, a VirtualBox module), and for that the right tool is DKMS, which we meet next.


3. Loadable kernel modules

A loadable kernel module (LKM) is a chunk of kernel code — a .ko (“kernel object”) file — that can be inserted into or removed from the running kernel without a reboot. Drivers, filesystems, netfilter/nftables hooks, and crypto algorithms are nearly all modules. This is how one kernel image supports thousands of devices without being gigantic: it loads only the modules the hardware present actually needs, on demand. Modules for your running kernel live under /lib/modules/$(uname -r)/ (a symlink target of /usr/lib/modules/... on some distros).

# Where your modules live, and how many there are
ls /lib/modules/$(uname -r)/
find /lib/modules/$(uname -r)/kernel -name '*.ko*' | wc -l
build  kernel  modules.alias  modules.dep  modules.order  modules.symbols  ...
6417

Seeing what’s loaded: lsmod

lsmod | head -6
Module                  Size  Used by
nf_conntrack          188416  3 xt_conntrack,nf_nat,nf_conntrack_netlink
xfs                  2555904  1
overlay               188416  2
dummy                  16384  0
bridge                311296  1 br_netfilter

Three columns: the Module name, its Size in bytes, and Used by — a reference count followed by the names of the modules that depend on it. lsmod is nothing but a formatter for the file /proc/modules; prove it with cat /proc/modules | head. That “Used by” count is the single most important number for troubleshooting: you cannot remove a module whose count is above zero. dummy above has a count of 0 and can be unloaded; nf_conntrack has 3 and cannot until its dependents go first.

Inspecting a module before you touch it: modinfo

modinfo dummy
filename:       /lib/modules/6.8.0-31-generic/kernel/drivers/net/dummy.ko.zst
license:        GPL
description:    Dummy net driver
depends:
retpoline:      Y
intree:         Y
name:           dummy
vermagic:       6.8.0-31-generic SMP preempt mod_unload
sig_id:         PKCS#7
signer:         Build time autogenerated kernel key
parm:           numdummies:Number of dummy pseudo devices (int)
modinfo field Tells you
filename Path to the .ko on disk (note .zst/.xz compression)
license GPL etc.; a non-GPL module “taints” the kernel
depends Other modules it needs (modprobe loads these first)
vermagic The exact kernel it was built for — must match or load fails
intree Y = shipped with the kernel; blank = out-of-tree
signer / sig_id Its cryptographic signature (matters under Secure Boot)
parm Tunable parameters you can pass, with type and description

That parm: line is gold — it tells you a module accepts numdummies, an int. modinfo -p dummy lists only the parameters. And vermagic explains the classic failure: a module built for a different kernel refuses to load, because its version magic doesn’t match the running kernel.

Loading and unloading: modprobe, insmod, rmmod

There are two tools that insert a module and they are not equals.

# The RIGHT way — resolves dependencies, searches by name
sudo modprobe dummy

# The low-level way — takes a full path, resolves NOTHING
sudo insmod /lib/modules/$(uname -r)/kernel/drivers/net/dummy.ko.zst

Always prefer modprobe. Here is why, mechanically. Each module may depend on others (its depends: line). The map of every module’s dependencies is precomputed into /lib/modules/$(uname -r)/modules.dep by a tool called depmod. modprobe reads that map and inserts every prerequisite in the correct order before inserting the module you named — by name, searching the tree for you. insmod does none of this: you hand it an exact file path, and if that module needs a symbol from another module that isn’t loaded yet, insmod fails with “Unknown symbol in module.” insmod is a scalpel for kernel developers; modprobe is what you use in real life.

Command Purpose Resolves deps? Takes
modprobe <name> Load a module and its dependencies ✅ (via modules.dep) Module name
modprobe -r <name> Unload a module and now-unused deps Module name
modprobe --show-depends <name> Print the insert order without loading Module name
modprobe -c Dump the full effective modprobe config
insmod <path.ko> Insert exactly one module file File path
rmmod <name> Remove exactly one module Module name
depmod -a Rebuild modules.dep/modules.alias after adding modules
lsmod List loaded modules (formats /proc/modules)
modinfo <name> Show a module’s metadata + parameters

To unload, modprobe -r is the mirror of modprobe — it also removes dependencies that are no longer used. rmmod removes exactly one module and nothing else.

⚠️ rmmod on an in-use module. If a module’s lsmod “Used by” count is non-zero, rmmod refuses: rmmod: ERROR: Module xfs is in use. The count is a real reference count — unloading a live driver would rip the rug out from under running code and can panic the kernel. Never reach for rmmod -f (force-unload) to get around this: it requires a specially built kernel and, when it works, frequently crashes the machine. The correct move is to stop whatever is using the module (unmount the filesystem, down the interface, kill the process) so the count drops to zero, then unload normally.

Loading a module with parameters

You can pass parameters at load time on the command line, or configure them permanently.

# Load 'dummy' asking for two virtual interfaces
sudo modprobe dummy numdummies=2
ip -br link show type dummy
dummy0           DOWN           7a:1c:...
dummy1           DOWN           2e:9b:...

The current value of every parameter of a loaded module is readable under /sys/module/<name>/parameters/ — another example of /sys exposing kernel state:

cat /sys/module/dummy/parameters/numdummies
2

To set a parameter permanently, drop an options line into /etc/modprobe.d/. These .conf files are read by modprobe (and by the udev auto-load path) every time the module is loaded.

# Persist a parameter across reboots
echo 'options dummy numdummies=2' | sudo tee /etc/modprobe.d/dummy.conf
Directive in /etc/modprobe.d/*.conf Does
options <mod> key=val … Set default parameters whenever <mod> loads
blacklist <mod> Stop auto-loading of <mod> (not explicit modprobe)
install <mod> /bin/false Replace loading of <mod> with a command — the hard block
install <mod> … Run an arbitrary command instead of the normal insert
alias <name> <mod> Map an alias (e.g. alias eth0 e1000e) to a module
softdep <mod> pre: <a> post: <b> Order-load helper modules around <mod>

Blacklisting: keeping a module OUT

Sometimes you need a module to never load — a buggy driver, one that conflicts with a vendor build, or a wireless card you are disabling for security. This has a famous gotcha, so read carefully.

# Prevent the Nouveau GPU driver from auto-loading (classic before installing NVIDIA's)
echo 'blacklist nouveau' | sudo tee /etc/modprobe.d/blacklist-nouveau.conf

blacklist nouveau stops the automatic load path — udev matching a PCI ID to the module and pulling it in. But blacklist does not stop an explicit modprobe nouveau, and it does not stop another module from pulling it in as a dependency. If you truly must nail a module shut, add the install override, which tells modprobe to run /bin/false instead of ever inserting the module:

# The belt-and-braces block
printf 'blacklist nouveau\ninstall nouveau /bin/false\n' | \
  sudo tee /etc/modprobe.d/blacklist-nouveau.conf

Because blacklists and driver changes affect the early boot environment too, you often must rebuild the initramfs so the change is present before the real root is mounted. This differs by distro:

Distro family Rebuild initramfs after a module/blacklist change
Debian / Ubuntu sudo update-initramfs -u -k "$(uname -r)"
RHEL / Fedora / Rocky sudo dracut -f

Auto-loading at boot: the mirror image

The opposite need — force a module to load on every boot even if nothing has requested it yet — is served by /etc/modules-load.d/, read by systemd-modules-load.service at boot. (Debian also honours the older /etc/modules file.)

# Load the 'br_netfilter' module on every boot (common for Kubernetes nodes)
echo 'br_netfilter' | sudo tee /etc/modules-load.d/k8s.conf
Path Read by Purpose
/lib/modules/$(uname -r)/modules.dep modprobe The dependency map (built by depmod)
/etc/modprobe.d/*.conf modprobe, udev options, blacklist, install, alias
/usr/lib/modprobe.d/*.conf modprobe Distro-shipped defaults (don’t edit; override in /etc)
/etc/modules-load.d/*.conf systemd-modules-load Modules to force-load at boot
/etc/modules Debian init Legacy force-load list

Signed modules, Secure Boot, and DKMS

On a machine with UEFI Secure Boot enabled, the kernel refuses to load a module unless it carries a trusted cryptographic signature — this stops an attacker loading a malicious rootkit-as-module. Distro modules are signed by the distro’s key, which the kernel trusts. An out-of-tree module you build yourself is not, so it will be rejected with a dmesg line like Loading of unsigned module ... rejected or module verification failed: signature and/or required key missing (E). The fix is to enrol your own Machine Owner Key (MOK) with mokutil --import (you confirm it in a blue firmware screen at the next reboot) and sign the module with that key.

# Is Secure Boot on? (needs the mokutil package)
mokutil --sb-state
SecureBoot enabled

This is exactly the pain point that DKMS (Dynamic Kernel Module Support) exists to smooth. When you install an out-of-tree driver (NVIDIA, VirtualBox, ZFS, a Wi-Fi chipset) via your package manager, DKMS registers its source. On every kernel upgrade, DKMS automatically recompiles the module against the new kernel — and on distros configured for it, re-signs it — so your GPU still works after apt upgrade pulls a new kernel. Without DKMS you would rebuild by hand after every kernel bump.

# What out-of-tree modules is DKMS managing, and for which kernels?
dkms status
nvidia/550.90.07, 6.8.0-31-generic, x86_64: installed
virtualbox/7.0.18, 6.8.0-31-generic, x86_64: installed

For DKMS to build anything it needs the matching kernel headers: linux-headers-$(uname -r) on Debian/Ubuntu, kernel-devel on RHEL/Fedora. A “module missing after kernel upgrade” incident is almost always a missing-headers or failed-DKMS-build problem, and dkms status plus dmesg tell you which.


4. /proc: the process and system window

procfs, mounted at /proc, is two interfaces sharing one directory. Its numbered entries are one directory per running process — this is the raw truth behind ps, top, and every process tool. Its named entries are system-wide kernel state. Nothing under /proc exists on disk; the kernel manufactures each file’s contents at the moment you read it.

Per-process entries: /proc/<pid>/

Every process has a directory named for its PID. /proc/self is a magic symlink to your current process’s directory, handy in scripts.

# Start a background sleeper and explore its /proc directory
sleep 600 &
PID=$!
ls /proc/$PID/
attr  cgroup  cmdline  comm  cwd  environ  exe  fd  limits  maps  mounts
oom_score  oom_score_adj  root  stat  status  task  wchan  ...
# The command line, NUL-separated — translate the NULs to spaces
tr '\0' ' ' < /proc/$PID/cmdline; echo
sleep 600
/proc/<pid>/ entry What it exposes Everyday use
cmdline The exact argv (NUL-separated) See a process’s real invocation
comm Short command name (≤15 chars) Quick label
status Human-readable state: VmRSS, Threads, State, UIDs Memory & thread count without ps
stat Raw single-line stats (what ps parses) Scripting
fd/ Symlinks to every open file descriptor Find leaked FDs, deleted-but-open files
maps The full memory map (libraries, heap, stack) Debug memory layout / mmap
environ The process’s environment (NUL-separated) See what env a service actually got
cwd Symlink to working directory Where is it running from
exe Symlink to the executable on disk Identify the binary (even if deleted)
limits Effective ulimits (open files, stack, …) Verify LimitNOFILE actually applied
oom_score / oom_score_adj OOM-killer target score / your bias Protect or sacrifice a process
root Symlink to the process’s root (chroot/container) Detect namespacing

Two of these solve problems that stump beginners. fd/ reveals files that are deleted but still held open (a classic “disk is full but du shows nothing” cause — a log file was rm’d while a daemon still writes to it):

# Which open files does our sleeper hold? (0,1,2 = stdin/out/err)
ls -l /proc/$PID/fd/

environ answers “what environment did this service actually receive?” — invaluable when a systemd unit misbehaves because a variable wasn’t set (reading another process’s environ needs root or matching ownership):

sudo tr '\0' '\n' < /proc/$PID/environ | head
kill $PID   # clean up our sleeper

System-wide entries

The un-numbered files in /proc are the system’s vital signs. You have met many already through friendly tools; here they are at the source.

/proc file Contents The friendly tool over it
/proc/cpuinfo Per-core CPU model, flags, MHz lscpu
/proc/meminfo Every memory counter (MemFree, Cached, Slab…) free, top
/proc/loadavg 1/5/15-min load + running/total procs uptime
/proc/uptime Seconds since boot + idle seconds uptime
/proc/mounts Every mounted filesystem (live) mount, findmnt
/proc/cmdline The kernel command line for this boot
/proc/version Kernel version + compiler + build date uname -a
/proc/stat Cumulative CPU/interrupt/context-switch counters vmstat, mpstat
/proc/interrupts Per-CPU interrupt counts by device debugging IRQ storms
/proc/swaps Active swap areas and usage swapon --show
/proc/modules Loaded modules (raw) lsmod
/proc/filesystems Filesystem types the kernel understands
/proc/partitions Block devices and sizes the kernel sees lsblk
/proc/sys/ The sysctl tree (tunables as files) sysctl
/proc/net/ Network stack tables (tcp, dev, …) ss, ip
# Load average and memory, straight from the kernel
cat /proc/loadavg
grep -E '^(MemTotal|MemAvailable|SwapTotal):' /proc/meminfo
0.14 0.09 0.03 1/412 20531
MemTotal:        8123456 kB
MemAvailable:    6210344 kB
SwapTotal:       2097148 kB

Notice /proc/sys/ in the table — that entire subtree is the sysctl interface, which is important enough to get its own section. But first, the other pseudo-filesystem, which handles devices rather than processes.


5. /sys and sysfs: the device and kernel-object model

Where /proc grew organically into a grab-bag of process and system files, sysfs (mounted at /sys) was designed with one clean idea: expose the kernel’s internal object model — every device, bus, driver, and class — as a navigable tree of directories, one attribute per file. If /proc is “how are things running,” /sys is “what is the hardware, and what can I set on it.” It is the data source udev uses to name your network interfaces and populate /dev, and the place you tune per-device behaviour.

ls /sys/
block  bus  class  dev  devices  firmware  fs  kernel  module  power
/sys area Contains You’d go here to
/sys/devices The canonical device tree (by physical topology) See the real hardware hierarchy
/sys/class Devices grouped by function (net, block, tty, thermal…) Find “all network interfaces” quickly
/sys/block Every block device + its queue/ tunables Set I/O scheduler, see rotational flag
/sys/bus Devices and drivers per bus (pci, usb, scsi…) Bind/unbind drivers, rescan
/sys/module One dir per loaded module + parameters/ Read/tune live module parameters
/sys/class/net Each NIC: address, mtu, statistics/, operstate Read link state and counters
/sys/kernel/mm Memory-management knobs (THP, ksm) Toggle transparent hugepages
/sys/devices/system/cpu Per-CPU: online, cpufreq/ governor Set frequency scaling policy
/sys/firmware ACPI/EFI tables, DMI Inspect firmware data
/sys/fs Per-filesystem + cgroup2 tunables cgroups, ext4/xfs knobs

Most /sys files are a single value you can cat to read and, where writable, echo to change. A few of the tunables you will actually touch:

# Which I/O scheduler is a disk using? (brackets show the active one)
cat /sys/block/sda/queue/scheduler
none mq-deadline [bfq]
# Is transparent hugepage on? (databases often want it off)
cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never
# Set the CPU frequency governor to performance (per core)
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
powersave
Example /sys tunable Reads / sets Notes
/sys/block/<dev>/queue/scheduler Active I/O scheduler none, mq-deadline, bfq, kyber
/sys/block/<dev>/queue/rotational 1 HDD / 0 SSD Drives scheduler defaults
/sys/block/<dev>/queue/read_ahead_kb Read-ahead size Tune for sequential I/O
/sys/kernel/mm/transparent_hugepage/enabled THP policy never for Mongo/Redis/Oracle
/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor Freq governor performance vs powersave
/sys/class/net/<if>/mtu Interface MTU Read (set via ip, not here, on live NICs)
/sys/class/net/<if>/statistics/rx_bytes RX/TX counters Raw byte/packet/error counts
/sys/module/<mod>/parameters/<p> A module’s live parameter Some are writable at runtime

⚠️ /sys writes do not persist. Echoing a value into /sys changes it now and reverts on the next reboot. To make a /sys tunable stick, use the right persistence mechanism: a udev rule (/etc/udev/rules.d/) for per-device attributes, a tuned profile (see the performance-tuning lesson), a small systemd unit, or — for things like THP — a kernel command-line parameter such as transparent_hugepage=never. Do not try to write /sys from /etc/sysctl.d/; sysctl only covers /proc/sys.

How udev uses /sys

When you plug in a device, the kernel creates its /sys objects and fires a uevent over netlink. udev (as systemd-udevd) catches that event, reads the device’s attributes from /sys, applies rules in /etc/udev/rules.d/ and /lib/udev/rules.d/, and then does things like create the /dev node, assign a persistent interface name (enp3s0 from PCI topology rather than the unstable eth0), or run a helper. You can inspect exactly what udev sees:

# Everything udev knows about the first network interface
udevadm info -q all -p /sys/class/net/$(ls /sys/class/net | grep -v lo | head -1)
# Watch device events live — plug a USB stick in while this runs
udevadm monitor --udev --property

This closes the loop from the diagram: /sys is the device model, netlink carries the events, and udev turns them into the /dev nodes and stable names userspace relies on.


6. sysctl and dmesg: runtime knobs and the kernel’s voice

Two interfaces remain: the one you use to change the kernel’s behaviour while it runs, and the one you use to hear what the kernel has to say. They are the day-to-day workhorses of production Linux.

sysctl: tuning the running kernel

sysctl reads and writes kernel parameters exposed under /proc/sys/. The trick that demystifies it: a sysctl name is just the path under /proc/sys/ with slashes turned into dots. net.ipv4.ip_forward is the file /proc/sys/net/ipv4/ip_forward. Anything sysctl does, you could do with cat and echosysctl is a friendlier, safer front-end with config-file management on top.

# These three are equivalent reads
sysctl net.ipv4.ip_forward
sysctl -n net.ipv4.ip_forward       # value only, no "key = "
cat /proc/sys/net/ipv4/ip_forward
net.ipv4.ip_forward = 0
0
0
Command Does
sysctl -a Dump every tunable and its current value
sysctl <key> Read one value (as key = value)
sysctl -n <key> Read just the value
sysctl -w <key>=<val> Set a value now (lost on reboot)
sysctl -p [file] Load settings from a file (default /etc/sysctl.conf)
sysctl --system Load all drop-in files in the correct precedence order
sysctl -a --pattern 'vm\.' Filter the dump by regex

To set a value for the current boot:

# Enable IP forwarding right now (routers, VPNs, containers, k8s)
sudo sysctl -w net.ipv4.ip_forward=1

But -w is volatile — it reverts on reboot. To persist a tunable, write it into a drop-in file under /etc/sysctl.d/ and apply with sysctl --system. This is the single most-tested sysadmin skill in this section, so learn the pattern exactly:

# Persist across reboots
cat <<'EOF' | sudo tee /etc/sysctl.d/99-tuning.conf
net.ipv4.ip_forward = 1
vm.swappiness = 10
fs.inotify.max_user_watches = 524288
EOF
sudo sysctl --system        # apply all drop-ins now, in precedence order

Precedence matters because several directories may set the same key. systemd-sysctl collects files from all of these directories, and the rules are: for two files with the same name, the one in the higher-priority directory wins; then the surviving files are applied in lexicographic filename order, so a later name (like 99-…) overrides an earlier one (10-…) for the same key.

Directory Priority Who puts files here
/etc/sysctl.d/*.conf Highest You — your overrides live here
/run/sysctl.d/*.conf Middle Runtime-generated (volatile)
/usr/lib/sysctl.d/*.conf Lowest Distro/package defaults (don’t edit)
/etc/sysctl.conf Applied as a drop-in (legacy single file) Historical; prefer /etc/sysctl.d/

The convention is to prefix filenames with two digits (99-tuning.conf) so ordering is explicit, and to keep your changes in /etc/sysctl.d/ — never edit the distro files in /usr/lib/sysctl.d/, or an update will overwrite them. Note that legacy sysctl -p with no argument loads only /etc/sysctl.conf; use sysctl --system to load the whole drop-in tree.

The knobs themselves fall into four families. Here are the ones you will actually reach for, with real defaults.

vm.* — the memory manager.

Knob Typical default What it controls
vm.swappiness 60 Eagerness to swap (lower = keep pages in RAM; 110 for DB hosts)
vm.overcommit_memory 0 0 heuristic, 1 always allow (Redis wants this), 2 strict accounting
vm.max_map_count 65530 Max memory-map areas per process (Elasticsearch needs 262144)
vm.dirty_ratio / vm.dirty_background_ratio 20 / 10 % of RAM of dirty pages before sync throttles / background flush starts
vm.min_free_kbytes sized to RAM Reserve the kernel keeps free to avoid deadlock
vm.drop_caches write-only Write 1/2/3 to drop caches (⚠️ diagnostic only)

net.* — the network stack.

Knob Typical default What it controls
net.ipv4.ip_forward 0 Route packets between interfaces (routers, VPNs, containers)
net.core.somaxconn 128 (4096 on ≥5.4) Max accept-queue backlog (raise for busy web servers)
net.ipv4.tcp_syncookies 1 SYN-flood protection
net.core.rmem_max / wmem_max ~212992 Max socket receive/send buffer (raise for high-bandwidth links)
net.ipv4.conf.all.rp_filter 1/2 Reverse-path filtering (⚠️ can break asymmetric routing)
net.ipv4.ip_local_port_range 32768 60999 Ephemeral port range (widen for many outbound connections)
net.netfilter.nf_conntrack_max sized to RAM Max tracked connections (raise on firewalls/NAT gateways)

kernel.* and fs.* — the core and filesystem layer.

Knob Typical default What it controls
kernel.pid_max 32768 Highest PID (raise on huge machines: 4194304)
kernel.panic 0 Seconds to auto-reboot after a panic (0 = never; set 10 on servers)
kernel.dmesg_restrict 1 on Ubuntu Whether non-root may read dmesg
kernel.sysrq 176/1 Magic SysRq key bitmask (⚠️ powerful)
kernel.core_pattern distro-set Where core dumps go (often piped to systemd-coredump)
fs.file-max sized to RAM System-wide max open file handles
fs.inotify.max_user_watches 8192 Watches per user (raise to 524288 for IDEs/file-watchers)
fs.inotify.max_user_instances 128 inotify instances per user

⚠️ Dangerous sysctls. A wrong value here can hang or expose a box. vm.overcommit_memory=1 lets allocations always succeed — great for Redis, a route to the OOM killer for a general host. Writing 3 to vm.drop_caches on a busy database evicts the page cache and tanks performance while it re-warms — it is a diagnostic toy, never a “free up memory” fix. Disabling net.ipv4.conf.all.rp_filter can silently break connectivity on asymmetrically-routed hosts. kernel.sysrq and /proc/sysrq-trigger can reboot the machine instantly (echo b > /proc/sysrq-trigger reboots with no sync — data loss). Change one knob at a time, know its default so you can revert, and test before you persist.

dmesg: the kernel ring buffer

The kernel cannot printf to your terminal — it has no terminal, and it runs long before and after any shell. So every message the kernel emits, via its internal printk() function, is written into a fixed-size, in-memory circular buffer: the ring buffer. When it fills, the oldest lines are overwritten. dmesg is the tool that prints that buffer. This is the first place to look for anything hardware-, memory-, or driver-related, because the kernel narrates those events here and nowhere else.

# Human-friendly: relative timestamps, colour, paged
dmesg -H | tail -15
[Jul 9 10:14] usb 1-2: new high-speed USB device number 4 using xhci_hcd
[  +0.148903] usb-storage 1-2:1.0: USB Mass Storage device detected
[  +1.203114] sd 6:0:0:0: [sdb] 30310400 512-byte logical blocks: (15.5 GB/14.5 GiB)
[Jul 9 10:15] EXT4-fs (sdb1): mounted filesystem with ordered data mode
dmesg flag Effect
-H Human: relative time, colour, sent to a pager
-w Wait/follow — stream new messages live (like tail -f)
-T Human wall-clock timestamps (⚠️ can drift after suspend)
-l err,crit Show only messages at these levels
-f kern Filter by facility
-k Kernel messages only
-x Decode facility and level for each line
-t No timestamps (clean copy-paste)
--clear / -C Empty the ring buffer (⚠️ you lose history)
-n <level> Set the console log level

Every message carries a log level from printk, 0 (most urgent) to 7 (debug). Filtering by level is how you cut through noise to find the problem.

# Level Macro Meaning
0 emerg KERN_EMERG System is unusable (panic imminent)
1 alert KERN_ALERT Action must be taken immediately
2 crit KERN_CRIT Critical condition (hardware, serious failure)
3 err KERN_ERR Error (I/O error, driver failure)
4 warning KERN_WARNING Warning
5 notice KERN_NOTICE Normal but significant
6 info KERN_INFO Informational (device probing)
7 debug KERN_DEBUG Debug-level chatter
# Show only errors and worse — the fast path to a real problem
sudo dmesg -l err,crit

The console log level (which messages print to a physical console during boot) is controlled by /proc/sys/kernel/printk — four numbers: current, default, minimum, boot-time default. The quiet kernel parameter simply lowers the first number so boot is silent. You can raise or lower it live with sysctl kernel.printk or dmesg -n <level>.

Reading real messages. The value of dmesg is recognising the important lines. These are the ones that answer support tickets:

Message pattern in dmesg Means Your move
Out of memory: Killed process 1234 (java) The OOM killer killed a process to save the system Add RAM, set limits, tune oom_score_adj, fix the leak
segfault at 0 ip … sp … error 4 in libX.so A process crashed (invalid memory access) Match the binary/library; check for corruption or a bug
blk_update_request: I/O error, dev sda, sector … The disk returned a read/write error Check SMART; the drive may be failing
EXT4-fs error … / remounting filesystem read-only Filesystem detected corruption fsck on next boot; investigate the disk
nvme nvme0: I/O … timeout, aborting NVMe device stopped responding Firmware/thermal/cabling; check /sys/class/nvme
<iface>: Link is Up/Down Network link state changed Correlate with cabling/switch/driver events
mce: … Machine check events logged A CPU/memory hardware error (ECC) mcelog/ras-mc-ctl; suspect RAM/CPU
module verification failed: … key missing An unsigned module rejected (Secure Boot) Enrol a MOK and sign it, or disable Secure Boot

One crucial limitation: the ring buffer is volatile and holds only the current boot. If a machine crashed and rebooted, its dmesg is gone. To read a previous boot’s kernel log you need the persistent journal, using the kernel-only filter of journalctl — this is where dmesg and journald connect:

# The kernel ring buffer, from the journal, for the PREVIOUS boot
journalctl -k -b -1 --no-pager | tail -20

journalctl -k (-k = --dmesg) shows the same kernel messages dmesg does, but because journald can persist across reboots (when /var/log/journal/ exists), it can travel back to boots that dmesg has long since lost. For deep, live kernel tracing beyond static messages — attaching probes to functions, counting events, watching syscalls in flight — Linux offers eBPF, bpftrace, perf, and ftrace (via /sys/kernel/tracing), which are a subject of their own beyond this fundamentals lesson.


Hands-on lab

Run this on any throwaway Linux VM, WSL2 instance, or container with a real kernel (a full VM is best; some /sys/module steps are limited in containers). You need sudo. Everything here is safe and fully reversible — we load a harmless dummy network module, read the interfaces, tune a knob, then undo it all.

Step 1 — Identify your kernel.

uname -r
grep CONFIG_DUMMY= /boot/config-$(uname -r)

You should see your kernel release and CONFIG_DUMMY=m (the dummy driver is a module). What just happened: you confirmed the kernel version and that the module we’re about to load exists as an .ko.

Step 2 — Inspect the module before loading it.

modinfo dummy | grep -E '^(filename|depends|vermagic|parm)'

You’ll see its path, its vermagic (must match uname -r), and the numdummies parameter. What just happened: you read a module’s metadata without touching the kernel.

Step 3 — Watch the kernel talk while you load it. Open a second terminal and run:

sudo dmesg -w        # live-follow the ring buffer; leave this running

Back in the first terminal:

sudo modprobe dummy numdummies=1

The dmesg -w window prints a line about dummy0. What just happened: modprobe inserted the module (resolving any deps), and the kernel announced the new device in the ring buffer.

Step 4 — See it through three interfaces.

lsmod | grep dummy                               # /proc/modules
ip -br link show type dummy                       # the new device
cat /sys/module/dummy/parameters/numdummies       # /sys sees your parameter

lsmod shows dummy … Used by 0; ip shows dummy0; /sys shows 1. What just happened: the same loaded module is visible through /proc (lsmod), the network stack, and /sys — three windows onto one kernel object.

Step 5 — Read process truth from /proc.

sleep 300 &
PID=$!
tr '\0' ' ' < /proc/$PID/cmdline; echo
grep -E '^(State|VmRSS|Threads):' /proc/$PID/status
ls -l /proc/$PID/fd/
kill $PID

You see the sleeper’s command line, memory, thread count, and open file descriptors — the raw data behind ps/top. What just happened: you read live per-process state directly from procfs.

Step 6 — Tune a sysctl temporarily, then persist it.

sysctl vm.swappiness                     # note the current value (e.g. 60)
sudo sysctl -w vm.swappiness=10          # change it NOW
cat /proc/sys/vm/swappiness              # 10 — the same file sysctl wrote

Now make it survive reboot, then apply:

echo 'vm.swappiness = 10' | sudo tee /etc/sysctl.d/99-lab.conf
sudo sysctl --system 2>/dev/null | grep swappiness

What just happened: -w changed a live /proc/sys file; the drop-in file makes it permanent; --system re-applied every drop-in in precedence order.

Step 7 — Explore /sys device tunables (read-only, safe).

for d in /sys/block/sd* /sys/block/nvme* /sys/block/vd*; do
  [ -e "$d" ] && echo "$d: sched=$(cat $d/queue/scheduler) rotational=$(cat $d/queue/rotational)"
done
cat /sys/kernel/mm/transparent_hugepage/enabled

You see each disk’s active I/O scheduler and whether the kernel thinks it’s rotational, plus the THP policy. What just happened: you read the device model and MM tunables from sysfs without changing anything.

Step 8 — Unload and clean up completely.

sudo modprobe -r dummy                   # unload the module + unused deps
lsmod | grep dummy || echo "dummy unloaded"
sudo rm /etc/sysctl.d/99-lab.conf        # remove our persisted knob
sudo sysctl -w vm.swappiness=60          # restore the default (use yours from step 6)

Stop the dmesg -w in the second terminal with Ctrl-C. What just happened: you reversed every change — module gone, drop-in removed, swappiness restored. The box is exactly as you found it.


Common mistakes and troubleshooting

The module/kernel problem table below is the one to bookmark — it maps the symptom you’ll actually see to its real cause and fix.

Symptom Likely cause Fix
modprobe: FATAL: Module X not found Module isn’t built for this kernel, or depmod map is stale Confirm it exists: modinfo X; if you just added it, sudo depmod -a
insmod: ERROR: could not insert: Unknown symbol Used insmod, which doesn’t resolve dependencies Use modprobe X instead — it loads prerequisites first
modprobe: ERROR: could not insert: Invalid module format / Version magic … should be … Module’s vermagic ≠ running kernel (built for another kernel) Rebuild for this kernel (DKMS), or install the matching module package
rmmod: ERROR: Module X is in use Something still references it (Used by > 0 in lsmod) Stop the user (unmount FS, ip link down, kill process), then unload
Module keeps auto-loading despite blacklist blacklist only stops auto-load, not deps/explicit load Add install X /bin/false to /etc/modprobe.d/, then rebuild initramfs
module verification failed: … key missing (E) Secure Boot rejects an unsigned out-of-tree module Enrol a MOK (mokutil --import) and sign it, or (lab only) disable Secure Boot
Out-of-tree driver gone after kernel upgrade DKMS didn’t rebuild — missing headers Install linux-headers-$(uname -r) / kernel-devel; check dkms status
sysctl -w change vanished after reboot -w is runtime-only Persist in /etc/sysctl.d/99-*.conf + sudo sysctl --system
sysctl: cannot stat /proc/sys/…: No such file Knob doesn’t exist on this kernel, or its module isn’t loaded Check kernel version; some keys need a module (e.g. br_netfilter for bridge knobs)
dmesg: read kernel buffer failed: Operation not permitted kernel.dmesg_restrict=1 blocks non-root Use sudo dmesg, or journalctl -k
Process “Killed”, no app log Kernel OOM killer dmesg -l err / journalctl -k for “Out of memory: Killed process”
Wrong network interface name after adding a NIC udev renamed it from /sys topology Check udevadm info; pin a name with a udev rule if needed

Three gotchas deserve extra words, because they are where hours disappear.

blacklist is not a lock. The number-one wasted afternoon in this topic is discovering that a “blacklisted” module loaded anyway. blacklist nouveau only removes the alias that lets udev auto-load it from a PCI ID. A modprobe nouveau, a softdep, or another module depending on it will still bring it in. If you genuinely need a module dead, pair the blacklist with install <mod> /bin/false and rebuild the initramfs (update-initramfs -u or dracut -f), because the auto-load can happen inside the initramfs before your /etc config is even consulted. Verify after reboot with lsmod | grep <mod>.

vermagic mismatch after an upgrade. A module built against one kernel will not load on another — the vermagic string encodes the exact version, SMP-ness, and preemption model, and the kernel checks it. This is by design: loading a mismatched module could corrupt memory. When a self-built or third-party driver “stops working after updates,” it’s almost always this. The right answer is DKMS, which recompiles the module for each new kernel automatically. Reading dmesg right after the failed modprobe shows the exact expected-vs-found version.

Persisting a /sys value in the wrong place. People try to put a /sys tunable (like the block scheduler or THP) into /etc/sysctl.d/ and are baffled when it’s ignored. sysctl only manages /proc/sys. A /sys attribute needs a different persistence mechanism — a udev rule, a tuned profile, a systemd unit, or a kernel parameter. Mixing up the two pseudo-filesystems is one of the most common conceptual errors, and the diagram at the top of this lesson exists precisely to keep them straight: /proc/sys = sysctl; /sys = the device model.


Cheat-sheet

Command / path What it does
uname -r Kernel release (use in /lib/modules/$(uname -r)/)
uname -a Full kernel identity line
cat /proc/cmdline Kernel command line for this boot
grep CONFIG_X /boot/config-$(uname -r) Was feature X built in (y), as a module (m), or not
lsmod List loaded modules (formats /proc/modules)
modinfo <mod> Module metadata: path, deps, vermagic, parm, signer
modinfo -p <mod> Just the module’s parameters
sudo modprobe <mod> Load module + dependencies (the right way)
sudo modprobe <mod> key=val Load with a parameter
sudo modprobe -r <mod> Unload module + now-unused deps
sudo modprobe --show-depends <mod> Show insert order without loading
sudo insmod <path.ko> Insert one module by path (no dep resolution)
sudo rmmod <mod> Remove one module (fails if in use)
sudo depmod -a Rebuild modules.dep after adding modules
/etc/modprobe.d/*.conf options, blacklist, install, alias
/etc/modules-load.d/*.conf Force-load modules at boot
cat /proc/<pid>/status A process’s memory, threads, state, UIDs
ls -l /proc/<pid>/fd/ A process’s open file descriptors
tr '\0' ' ' < /proc/<pid>/cmdline A process’s real argv
cat /proc/meminfo | /proc/loadavg | /proc/cpuinfo System memory / load / CPU
cat /sys/block/<dev>/queue/scheduler Active I/O scheduler (bracketed)
cat /sys/module/<mod>/parameters/<p> A loaded module’s live parameter
udevadm info -q all -p /sys/... What udev knows about a device
sysctl <key> / sysctl -n <key> Read a tunable (with / without the name)
sudo sysctl -w <key>=<val> Set a tunable now (volatile)
/etc/sysctl.d/99-*.conf + sudo sysctl --system Persist and apply tunables
dmesg -H Kernel ring buffer, human-friendly, paged
sudo dmesg -w Follow the ring buffer live
sudo dmesg -l err,crit Only error/critical kernel messages
journalctl -k -b -1 Kernel log from the previous boot
mokutil --sb-state Is Secure Boot enabled
dkms status Out-of-tree modules DKMS is managing

Interview and exam questions

Q: Why prefer modprobe over insmod? A: modprobe resolves dependencies. It reads /lib/modules/$(uname -r)/modules.dep (built by depmod) and inserts every prerequisite module in the correct order, searching by module name. insmod takes a raw file path and inserts exactly one module with no dependency handling, so it fails with “Unknown symbol” if a required module isn’t already loaded. Use modprobe in practice; insmod is a low-level developer tool.

Q: A blacklist line in /etc/modprobe.d/ isn’t stopping a module from loading. Why, and what actually works? A: blacklist only disables the automatic (udev alias-based) load path. It does not block an explicit modprobe, a softdep, or a module pulled in as a dependency. To truly prevent a load, add install <mod> /bin/false so any load attempt runs /bin/false instead — and rebuild the initramfs (update-initramfs -u or dracut -f) so the block applies during early boot too.

Q: What’s the difference between /proc and /sys? A: Both are pseudo-filesystems the kernel synthesises in memory. /proc (procfs) holds per-process directories (/proc/<pid>/) plus system-wide files (cpuinfo, meminfo) and the sysctl tree (/proc/sys). /sys (sysfs) is the structured device/driver/bus/class object model — one attribute per file — used by udev and for per-device tuning. Rule of thumb: process/system info and sysctls live in /proc; the device model lives in /sys.

Q: You set net.ipv4.ip_forward=1 with sysctl -w but it’s gone after a reboot. Fix it. A: -w is runtime-only. Persist it: write net.ipv4.ip_forward = 1 into a file such as /etc/sysctl.d/99-router.conf, then apply all drop-ins with sudo sysctl --system. On next boot systemd-sysctl reads the drop-in automatically.

Q: Explain the relationship between a sysctl name and a file path. A: A sysctl key is the path under /proc/sys/ with slashes replaced by dots. net.ipv4.ip_forward is /proc/sys/net/ipv4/ip_forward. sysctl -w key=val and echo val > /proc/sys/.../file are equivalent; sysctl just adds name translation and config-file management.

Q: A process died with only “Killed” in the output. How do you find out why? A: Check the kernel ring buffer: sudo dmesg -l err or journalctl -k. An OOM kill logs Out of memory: Killed process <pid> (<name>) with the memory state. If it was a crash rather than OOM you’ll see a segfault line. dmesg is the kernel’s voice and the first stop for any “no application log” death.

Q: rmmod xfs returns “Module is in use.” What now? A: The module’s reference count is above zero (visible as “Used by” in lsmod). Something is using it — here, a mounted XFS filesystem. Stop the user first (umount the XFS mounts), which drops the count to zero, then unload. Never use rmmod -f; force-unloading a live module typically panics the kernel.

Q: How do you tell whether a kernel feature is built-in, a module, or absent? A: Grep the config: grep CONFIG_X /boot/config-$(uname -r). =y means built into the kernel image, =m means a loadable module (an .ko you can modprobe), and # CONFIG_X is not set means it wasn’t compiled and needs a kernel rebuild to get.

Q (RHCSA-style task): Persistently disable transparent hugepages on a database host. A: THP is a /sys knob (/sys/kernel/mm/transparent_hugepage/enabled), not a sysctl, so persist it via the kernel command line: add transparent_hugepage=never to GRUB_CMDLINE_LINUX in /etc/default/grub, run sudo grub2-mkconfig -o /boot/grub2/grub.cfg (or grubby --update-kernel=ALL --args="transparent_hugepage=never"), and reboot. Verify with cat /sys/kernel/mm/transparent_hugepage/enabled showing [never].

Q (LFCS-style task): Load the br_netfilter module now and on every boot, and confirm. A: sudo modprobe br_netfilter loads it now; echo br_netfilter | sudo tee /etc/modules-load.d/br_netfilter.conf makes systemd-modules-load load it every boot. Confirm the running state with lsmod | grep br_netfilter.

Q: Why won’t a self-compiled module load after a kernel update, and what’s the durable fix? A: The module’s vermagic no longer matches the running kernel, and the kernel refuses the mismatch to protect memory integrity. The durable fix is DKMS: register the module’s source with DKMS so it automatically recompiles (and, where configured, re-signs) against each new kernel. Requires the matching linux-headers/kernel-devel.

Q: How do you read the kernel messages from the boot before the current one? A: dmesg only shows the current boot’s ring buffer (it’s volatile). Use the persistent journal: journalctl -k -b -1 shows kernel (-k) messages from the previous (-b -1) boot — provided persistent journaling is enabled (/var/log/journal/ exists).


Key takeaways

linuxkernelkernel-modulesmodprobelsmodprocsysfssysctldmesgkernel-parametersdkmssecure-bootinitramfsrhcsa
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments