Code that joins the kernel while it runs
From the earlier rungs you have a sharp picture of the boundary: your program lives in user space, the kernel lives across a hard wall, and you only cross that wall through a system call that traps into kernel mode. A device driver lives on the other side of that wall — it is kernel code. In a monolithic kernel like Linux, drivers run with full kernel privilege, in the kernel's own address space, with no protection between one driver and the next. A bug in a driver is not a segfault in one process; it can take down the whole machine. That is the stakes of this rung, and it is why we are careful.
Now the surprising part. You do not have to recompile the whole kernel and reboot every time you want a new driver. The kernel can accept new code while it is running. A loadable kernel module (LKM) is a chunk of object code — a .ko file, "kernel object" — that you can splice into the live kernel and later remove, all without rebooting. When you run a command like "sudo insmod mydriver.ko", the kernel loads that file, resolves its symbols against the kernel's own symbol table, runs its initialization function, and from that instant the module's code is part of the kernel, running at full privilege. It is the same idea as a shared library you met in the toolchain rung — late linking of separately compiled code — except the host being linked into is the live kernel rather than a user process.
The lifecycle: init, live, exit
Every module has exactly two ceremonial functions that bracket its life, and the whole module lifecycle hangs off them. The init function, marked with the macro module_init(), runs once at load time: it is where the module grabs the resources it needs — registers itself with a subsystem, allocates kernel memory with kmalloc(), asks for an interrupt line, claims a device number. The exit function, marked module_exit(), runs once at unload time and must undo every single thing init did, in reverse. There is no garbage collector in the kernel; if exit forgets to free what init allocated, that memory is gone until reboot — a leak you cannot recover from inside a running kernel.
The init function returns an int: 0 for success, or a negative error code on failure. This is not decoration — it is a hard contract. If init returns nonzero, the kernel refuses to load the module and tears down whatever it had started. So the discipline you learned in the error-handling rung becomes literal here: check every call init makes, and on the first failure, undo what you have done so far and return the error. The classic shape is a ladder of goto-labels that unwind partial setup in reverse — exactly the goto cleanup pattern, which in kernel C is not a code smell but the idiomatic, correct way to release resources on a failure path.
insmod mydriver.ko
|
v
module_init(my_init) ---> returns 0 ---> module LIVE
| | (functions callable,
| | waiting for events)
| returns <0 --> load FAILS, kernel
| unwinds, .ko discarded
v
... time passes; driver code runs only when CALLED ...
|
v
rmmod mydriver (only if refcount == 0)
|
v
module_exit(my_exit) ---> frees/unregisters everything init grabbedUnloading safely, and passing parameters
Why can the kernel not just yank a module out whenever you say "rmmod"? Because some other thread might be inside the module's code at that very moment — reading from its device, halfway through one of its functions. Freeing the code out from under a running thread would be a use-after-free at the worst possible scope. The kernel guards against this with a reference count: every time something starts depending on the module — an open device file, a registered handler still in use — the count goes up; when that use ends, it goes down. rmmod only succeeds when the count is zero. This is the same ownership logic you met with allocation ownership in user space, now enforced by the kernel to decide when a module is safe to remove.
A real driver also needs to be configured — which I/O address, how big a buffer, whether to turn on debug logging. You cannot pass argc/argv to a module the way you would to a program, because a module has no command line. Instead the kernel offers module parameters: a variable inside the module, declared with module_param(), that the loader can set on the spot. You write "insmod mydriver.ko buffer_size=4096 debug=1" and those values land in the module's variables before init runs. They can even be read and changed afterward through a file under /sys, the kernel's window onto its own state. It is the kernel's tidy answer to the same need environment variables serve for an ordinary program.
The driver model: matching code to hardware
A module is just the delivery mechanism — the truck. The cargo is a driver, and a driver only matters because it binds to a specific piece of hardware. So how does the kernel know that this freshly loaded code is the right code to run a particular network card, and not someone else's? That matchmaking is the job of the driver model: a kernel-wide framework that keeps a unified picture of every device present, every driver registered, and every bus that connects them. Before this framework existed, every subsystem reinvented device tracking its own messy way; the driver model gave the kernel one coherent tree of devices and drivers, which is also what powers /sys.
The matching works through buses. Hardware hangs off buses — PCI, USB, I2C — and each device on a bus announces an identity, like a PCI vendor-and-device id pair such as 0x8086:0x100e. When the kernel walks a bus and discovers what is plugged in, a step called bus enumeration, it reads those ids. Each driver, meanwhile, registers a table saying "I support these ids." The bus core compares the two: when a discovered device's id matches a driver's table, the kernel calls that driver's probe function, handing it the device. Probe is where the driver finally wakes up and takes ownership of real hardware — and it is the mirror image of the driver abstraction's promise: the same generic open()/read()/write() calls reach any device, because under the hood the right probe wired the right driver to the right chip.
What this opens up, and the road through the rung
Step back and see what we have built. A driver is kernel code (full privilege, no net), packaged as a loadable module (spliced in and out of a live kernel), governed by a strict init/exit lifecycle (acquire then release, every resource, no leaks), configured by parameters, and bound to hardware by the driver model through bus enumeration and probe. That single sentence is the skeleton of everything in this rung; the remaining guides each flesh out one bone of it. None of them re-derive the module machinery — they assume the picture you now hold.
Here is the map. The next guide asks what kind of device this is, because the kernel sorts them into three families with different interfaces — character, block, and network devices — and the read()/write() face a driver presents lives in a structure of function pointers, the file_operations struct. After that we go to the moment hardware demands attention: an interrupt fires, and the driver must respond fast then defer the slow part. Then comes the hardest physical question — how does a driver actually move bytes to and from a chip, through memory-mapped I/O and DMA. The rung closes by weighing polling against interrupts, meeting the device tree that tells the kernel what hardware exists, and learning to ferry data across the wall safely with copy_to_user() and copy_from_user().