Date: Sun, 30 Mar 1997 10:25:38 +0100 (BST) From: Doug Rabson <dfr@nlsystems.com> To: current@freebsd.org Subject: A new Kernel Module System Message-ID: <Pine.BSF.3.95q.970330101633.1828A-100000@kipper.nlsystems.com>
next in thread | raw e-mail | index | archive | help
I want to re-vamp our kernel module system. The current system is fine as far as it goes but does have some serious limitations. I want to replace the current system with a new design which should be more flexible and should allow many more (hopefully all) device drivers to be supported as loadable modules. To avoid stepping on anyone's toes too early, I have written a paper showing how the new system will work. I would appreciate feedback, flaming or otherwise :-). --------------------------cut here------------------------ A new Kernel Module System A proposal to replace the current LKM system with a new implementation allowing both static and dynamically loaded modules. 1. The current LKM system 1.1. Description The current LKM system only supports dynamically loaded modules. Each module is either one of a small number of specially supported types or is a `catch all' misc module. The modules are a.out object files which are linked against the kernel's symbol table using ld(1). Each module has a single entry point which is called when the module is loaded and unloaded. 1.2. Lifecycle of an LKM The user initiates a module load (either by mounting a filesystem or by explicitly calling modload(8)). The module is loaded in three stages. First memory in the kernel address space is allocated for the module. Second, ld(1) is used to link the module's object file to run at the address which has been allocated for it. The kernel's symbol table is used to resolve external symbol references from the module. Lastly the relocated module is loaded into the kernel. The first thing the kernel does with the new module is to call its entry point to inform it that it has been loaded. If this call returns an error (e.g. because a device probe failed), the module is discarded. For syscalls, filesystems, device drivers and exec format handlers, common code in the lkm subsystem handles this load event. When the module is no longer needed, it can be unloaded using modunload(8). The module's entry point is called to inform it of the event (again this is handled in common code for most modules) and the kernel's memory is reclaimed. 1.3. Limitations Since the link stage is performed outside the kernel, modules can only be loaded after the system is fully initialised (or at least until after filesystems have been mounted). This makes automatic module loading during boot hard or impossible. Kernel initiated module loads (e.g. as a result of detecting a PCI device which is supported by a driver in a module) are virtually impossible. Statically loaded drivers initialise themselves using SYSINIT(9) along with various tables created by config(8) to add their entries to the various device switch tables. Making a statically loaded driver into a loadable module requires extra code to mimic this process. As a result, most drivers cannot be built as modules. 2. A new module system 2.1. Features · Support for both statically and dynamically loaded modules. · Dynamically loaded modules are relocated and linked by the kernel using a built in kernel symbol table. · Static loaded modules are identical to dynamic modules in every way. To include a static module in a kernel, the module's object file is simply included in the kernel's link. · Modules initialise and register themselves with the kernel using SYSINIT(9). · All devices drivers and filesystems and other subsystems are implemented as modules. · Statically loaded modules are informed when the system shuts down. System shutdown would appear to a statically loaded module as an unload event. Various drivers use at_shutdown(9) to tidy up device state before rebooting. This process can happen from the module's unload handler. · A desirable feature would be to support dependencies between modules. Each module would define a symbol table. If a module depends upon another, the dependant module's symbol table is used to resolve undefined symbols. 2.2. Kernel configuration Statically loaded modules are specified by a kernel configuration file, either implicitly by a controller, disk, tape or device keyword or explicitly with a new module keyword. 2.3. Devices Several types of device exist. Currently devices are configured into a kernel using various tables built by config(8) and ld(1). To make it easier to add devices and drivers to a running kernel, I suggest that all drivers use SYSINIT(9) to register themselves with the system. 2.3.1. ISA devices Currently ISA devices are included in a kernel by using config(8) to generate a list of device instances in ioconf.c which reference drivers statically compiled into the kernel. Few drivers support dynamic loading and those that do have hardcoded device instances built into the LKM (see sys/i386/isa/joy.c for an example). ISA drivers will register themselves by name using SYSINIT(9). This would happen either at boot time for static drivers or at module load time for dynamic drivers. Device instances (struct isa_device) will refer to their driver by name rather than by pointer. The name to driver mapping is performed and the device is probed and attached as normal. Statically configured devices are placed in a table by config(8) and modules containing their drivers are added to the kernel Makefile. When an ISA device is configured dynamically, first the module which contains its driver is loaded if not already present and secondly a system call is used to create a new device instance and to call the driver to probe and attach the new device. It is probably worth writing a new utility, isaconf(8), which can add new ISA device instances to the kernel. A desirable feature for a new module system would be to allow drivers to `detach' themselves from device instances, allowing a dynamically loaded driver to be unloaded cleanly. 2.3.2. PCI devices Currently each PCI driver has a pci_device structure which is included in a linker set. When a device is detected by the boot process, all driver probe functions are called in turn until the device is recognised. Again, in the new system, drivers should use SYSINIT(9) to register themselves with the PCI subsystem. Instead of a linker set, the pci_device (shouldn't this be pci_driver?) structures would be held on a linked list built by this registration process. At boot time, the PCI bus is scanned to generate a list of PCI device instances. The probes of all currently loaded drivers are called for these device instances. Any devices which are not assigned drivers are remembered. When a new driver module is loaded, the PCI subsystem re-scans unassigned devices when the module registers itself. This is broadly what happens for PCI drivers contained in LKMs today. If a driver is unloaded, it releases any resources such as interrupts allocated for devices attached to it. These devices become unassigned, as if they were not successfully probed. This allows driver developers to repeatedly load and unload modules without rebooting. 2.4. Dynamic loading Supporting static as well as dynamic modules makes the single module per object file paradigm of the existing LKM system difficult to maintain. A better approach is to separate the idea of a kernel module (a single kernel subsystem) from the idea of a kernel object file. The boot kernel should be thought of as simply a kernel object file which contains the modules that were configured statically. Dependencies between modules are also better treated as dependencies between object files (since they are typically linking dependencies). The new system will use a kernel linker which can load object files into the kernel address space. After loading, sysinits from the new object file are run, allowing any modules contained therein to register themselves. The linker will keep track of which modules are contained in which object so that when a user unloads the object, the modules can be informed of the event. Each object has a symbol table associated with it. When linking a new object into the kernel, the symbol tables of all the objects which it depends on are used to resolve undefined references. All modules implicitly depend on the boot kernel object and therefore can use symbols from the base kernel. Since objects have private symbol tables, a symbol with the same name can be used in more than one object without conflict; this is not a practice to be encouraged since it prevents modules in those objects from being linked statically. Each object has a reference count. Each time a dependant object is loaded, the reference count is increased. The user-initiated load also increases the reference count by one. To unload a module, the user will simply release this reference. The object will only be actually removed from the kernel if its reference count reaches zero. This scheme allows the automatic unloading of dependant modules as well as preventing an object from being removed while it is still in use. -- Doug Rabson Mail: dfr@nlsystems.com Nonlinear Systems Ltd. Phone: +44 181 951 1891
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?Pine.BSF.3.95q.970330101633.1828A-100000>