Devices and Modules

Device Types

  • 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.