The system is broadly separated into three main subsystems.
The kernel linker simply dynamically loads code into the kernel. A symbol table is included in the kernel by ld(1) in the same way as for dynamically linked user programs. As files are loaded, the code is relocated and any unresolved symbols are matched against the kernel's symbol table. Files can also include a list of dependencies to allow code which is common to several files to be loaded automatically. The kernel can load files without help from a user program (in contrast to the older LKM system) and the kernel bootstrap can also pre-load files, allowing devices which needed before the root disk is available to be dynamically loaded instead of statically linked into the kernel.
As code is loaded, any SYSINITs which it contains are run. This makes it possible to write code which is identical whether it is statically or dynamically loaded. When a file is unloaded, a similar list of functions defined by SYSUNINIT is run.
Layered on top of the kernel linker is the module system. It uses a SYSINIT to implement a simple event system for code which is loaded. The idea is that a piece of code defines a module (using DECLARE_MODULE) and supplies a handler routine. The handler is called at load, unload and shutdown to allow the module to initialise itself. Various kernel subsystems provide generic handler functions for registering filesystems, devices or whatever and they generally provide a macro which wraps DECLARE_MODULE (e.g. VFS_SET).
The main part the device system itself. There are three main objects in the device system, device_t, driver_t and devclass_t.
Devices are arranged in a tree with the special root device at the top. As the kernel code starts up, it adds devices to the root device which represent the system busses. The devices are probed and attached and busses add child devices to themselves as they are probed (leading to a depth-first traversal).
Drivers are simple structures containing a name, a list of methods and the size of the driver's private per-device memory requirements. They are registered via the module system using macros (DRIVER_MODULE, CDEV_DRIVER_MODULE etc.)
The devclass has two main roles. The first is to maintain a mapping from name+unit to a device. Each named driver (sio, ed or whatever) uses a devclass with the same name and the devclass contains a table which maps the unit number to the device instance. The second role (slightly confusing) is to store lists of drivers. All the drivers for a particular bus type are stored in the devclass for that bus type (e.g. the devclass with the name "pci" would keep the drivers for pci hardware). This list is used at probe time to match drivers to devices.