In Linux, devices are classified into three primary types: block, character, and network devices.
Block devices: Hardware devices addressed in fixed-size chunks (blocks) that support random access, such as hard drives and flash memory.
Character devices: Hardware devices accessed as a sequential stream of bytes, such as keyboards and serial ports, interacting directly through character device nodes.
Network devices: Hardware adapters providing network access via specific protocols, breaking the traditional “everything is a file” paradigm by utilizing the socket API instead of device nodes.
Miscellaneous devices: A simplified subset of character devices that trade complex functionality for common, shared infrastructure.
Pseudo devices: Virtual device drivers providing access to core kernel functionality rather than physical hardware, including /dev/random and /dev/null.
To interface with these distinct hardware types dynamically, the kernel bundles device drivers into loadable object binaries known as modules.
Kernel Modules
Modules are loadable kernel objects containing related subroutines, data, and entry/exit points that can be dynamically inserted and removed from the kernel address space at runtime.
Module initialization functions are registered via the module_init() macro.
Initialization routines must return zero upon success, or return a nonzero value and unwind initializations upon failure.
Module exit functions are registered via the module_exit() macro.
Exit routines clean up resources, reset hardware, and undo operations performed by the initialization function before the module is unloaded from memory.
The MODULE_LICENSE() macro explicitly declares the copyright license.
Loading a module without a GPL-compatible license sets a tainted flag in the kernel and restricts the module from accessing GPL-only exported symbols.
Modules require specific build configurations and parameter controls to integrate with the kernel’s compilation and initialization processes.
Building and Configuring Modules
The Kbuild System
Modules built within the kernel source tree are specified in subdirectories with a Makefile directive, such as obj-m += module.o.
For modules spanning multiple source files, objects are linked together using the -objs suffix, formatting as module-objs := file1.o file2.o.
Modules built externally utilize the same Makefile syntax but invoke the build process by pointing make to the configured kernel source tree via the -C flag.
Managing Configurations
Configuration options are defined in Kconfig files using the config directive.
The tristate directive specifies that a feature can be built-in (Y), built as a module (M), or disabled (N), whereas bool restricts options to built-in or disabled.
Dependencies are strictly enforced using the depends on directive, while the select directive automatically forces associated options to be enabled.
Module Loading and Dependencies
Compiled modules install into /lib/modules/version/kernel/.
The depmod utility generates dependency mappings between modules, storing the output in modules.dep.
The modprobe utility provides advanced loading and unloading, automatically handling dependency resolution and error checking, superseding the basic insmod and rmmod tools.
Once loaded, modules often require dynamic runtime configuration, which is handled via parameters and shared symbol exports.
Module Parameters and Exported Symbols
Module Parameters
The module_param(name, type, perm) macro defines parameters that can be specified on boot or load, exposing them as global variables and mapping them to sysfs files.
Parameter data types include byte, short, integer, long, character pointers (charp), and booleans.
The perm argument assigns standard octal permissions to the resulting sysfs entry, or a value of zero to disable sysfs representation entirely.
module_param_string() copies a user-supplied string directly into a statically allocated character array.
module_param_array() parses a comma-separated list into a statically allocated C array.
Exported Symbols
Dynamically linked modules can only invoke external functions explicitly exported by the core kernel or other modules.
The EXPORT_SYMBOL() macro exposes a nonstatic function to any module.
The EXPORT_SYMBOL_GPL() macro restricts symbol availability exclusively to modules bearing a GPL license declaration.
Because modules manage discrete hardware devices, the kernel requires a unified structural representation of the system’s hardware topology.
The Device Model and Kobjects
The unified device model provides a framework to represent devices, detail their topology, minimize code duplication, and enforce correct power management sequencing.
Kobjects
The struct kobject serves as the base object class, establishing a device hierarchy via parent pointers and providing lifecycle management.
Kobjects are typically embedded within larger, subsystem-specific data structures (e.g., struct cdev), inheriting standardized hierarchical and reference counting features.
kobject_init() zero-initializes the object and prepares it, while kobject_create() dynamically allocates and initializes a new kobject.
Reference Counting and Krefs
Kobjects utilize reference counting to ensure objects remain pinned in memory while actively used.
kobject_get() elevates the reference count, while kobject_put() decrements it.
When the count reaches zero, the release function is triggered to deallocate the object and free memory.
Internally, this mechanism is provided by struct kref, which wraps an atomic_t variable to execute thread-safe reference counting via kref_init(), kref_get(), and kref_put().
To establish shared behavioral definitions and organizational groupings, kobjects depend on secondary structure definitions known as ktypes and ksets.
Ktypes and Ksets
Ktypes
struct kobj_type (ktype) dictates the default behavior for a family of kobjects.
The release pointer specifies the deconstructor function invoked when a kobject’s reference count zeroes.
The sysfs_ops pointer defines the file I/O behavior for reading and writing to sysfs entries.
The default_attrs array points to a default set of attribute structures mapped to sysfs files.
Ksets
struct kset acts as an aggregate container class, collecting related kobjects (e.g., all block devices) into a unified set.
Ksets manage a linked list of all grouped kobjects and define hotplug event rules through uevent_ops.
The internal hierarchy composed of kobjects, ktypes, and ksets is explicitly exported to user-space through a memory-based virtual filesystem.
Sysfs
The sysfs filesystem (typically mounted at /sys) is an in-memory virtual filesystem representing the kernel’s exact kobject hierarchy.
Directory Mapping
Kobjects map directly to sysfs directories via their embedded dentry structure.
The location of a kobject’s directory is determined by its parent pointer or, if unset, its associated kset.
kobject_add() links a kobject to a sysfs path, and kobject_del() removes the sysfs representation.
Attributes and File Operations
Kernel variables are mapped to sysfs files using struct attribute, specifying the file’s name and octal permissions.
The sysfs_ops structure implements the show() and store() functions, which handle read and write operations by copying data between the kernel attribute and a page-sized user buffer.
Custom attributes can be appended dynamically using sysfs_create_file() and removed via sysfs_remove_file().
Sysfs Conventions
Sysfs replaces deprecated ioctl() calls and disorganized /proc entries for device control.
Attributes must export only one text-based value per file to ensure intuitive access via shell scripts and user-space applications.
By exposing the hardware tree directly, sysfs provides the addressing scheme required to notify user-space of asynchronous hardware events.
The Kernel Events Layer
The Kernel Event Layer implements a notification system pushing hotplug and hardware state changes from kernel-space to user-space.
Events are modeled as signals emitting from specific kobjects, using the kobject’s sysfs path as the origin address.
Each signal executes a defined action string (verb), such as KOBJ_ADD or KOBJ_REMOVE, accompanied by optional payload data encoded as sysfs attributes.
Events are dispatched internally via kobject_uevent() and transmitted over high-speed netlink multicast sockets.
User-space daemons (such as D-BUS) block on the netlink socket, intercept the events, and distribute the hardware notifications up the application stack.