Loadable Kernel Modules

A device driver can be either statically compiled into the system or dynamically loaded using a loadable kernel module (KLD).

Note

Most operating systems call a loadable kernel module an LKM—FreeBSD just had to be different.

A KLD is a kernel subsystem that can be loaded, unloaded, started, and stopped after bootup. In other words, a KLD can add functionality to the kernel and later remove said functionality while the system is running. Needless to say, our “functionality” will be device drivers.

In general, two components are common to all KLDs:

  • A module event handler

  • A DECLARE_MODULE macro call

Module Event Handler

A module event handler is the function that handles the initialization and shutdown of a KLD. This function is executed when a KLD is loaded into the kernel or unloaded from the kernel, or when the system is shut down. Its function prototype is defined in the <sys/module.h> header as follows:

typedef int (*modeventhand_t)(module_t, int /* modeventtype_t */, void *);

Here, modeventtype_t is defined in the <sys/module.h> header like so:

typedef enum modeventtype {
        MOD_LOAD,       /* Set when module is loaded. */
        MOD_UNLOAD,     /* Set when module is unloaded. */
        MOD_SHUTDOWN,   /* Set on shutdown. */
        MOD_QUIESCE     /* Set when module is about to be unloaded. */
} modeventtype_t;

As you can see, modeventtype_t labels whether the KLD is being loaded into the kernel or unloaded from the kernel, or whether the system is about to shut down. (For now, ignore the value at ; we’ll discuss it in Chapter 4.)

Generally, you’d use the modeventtype_t argument in a switch statement to set up different code blocks for each situation. Some example code should help clarify what I mean:

static int
modevent(module_t mod __unused, int event, void *arg __unused)
{
        int error = 0;

        switch (event) {
      case MOD_LOAD:
                uprintf("Hello, world!
");
                break;
      case MOD_UNLOAD:
                uprintf("Good-bye, cruel world!
");
                break;
      default:
                error = EOPNOTSUPP;
                break;
        }

        return (error);
}

Notice how the second argument is the expression for the switch statement. Thus, this module event handler prints “Hello, world!” when the KLD is loaded into the kernel, prints “Good-bye, cruel world!” when the KLD is unloaded from the kernel, and returns EOPNOTSUPP (which stands for error: operation not supported) prior to system shutdown.

DECLARE_MODULE Macro

The DECLARE_MODULE macro registers a KLD and its module event handler with the system. Here is its function prototype:

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>

DECLARE_MODULE(name, moduledata_t data, sub, order);

The arguments expected by this macro are as follows.

name

The name argument is the module name, which is used to identify the KLD.

data

The data argument expects a filled-out moduledata_t structure, which is defined in the <sys/module.h> header as follows:

typedef struct moduledata {
        const char      *name;
        modeventhand_t  evhand;
        void            *priv;
} moduledata_t;

Here, name is the official module name, evhand is the KLD’s module event handler, and priv is a pointer to private data (if any exists).

sub

The sub argument specifies the kernel subsystem that the KLD belongs in. Valid values for this argument are defined in the sysinit_sub_id enumeration, found in <sys/kernel.h>.

enum sysinit_sub_id {
        SI_SUB_DUMMY            = 0x0000000,    /* Not executed.        */
        SI_SUB_DONE             = 0x0000001,    /* Processed.           */
        SI_SUB_TUNABLES         = 0x0700000,    /* Tunable values.      */
        SI_SUB_COPYRIGHT        = 0x0800001,    /* First console use.   */
        SI_SUB_SETTINGS         = 0x0880000,    /* Check settings.      */
        SI_SUB_MTX_POOL_STATIC  = 0x0900000,    /* Static mutex pool.   */
        SI_SUB_LOCKMGR          = 0x0980000,    /* Lock manager.        */
        SI_SUB_VM               = 0x1000000,    /* Virtual memory.      */
...
      SI_SUB_DRIVERS          = 0x3100000,     /* Device drivers.      */
...
};

For obvious reasons, we’ll almost always set sub to SI_SUB_DRIVERS, which is the device driver subsystem.

order

The order argument specifies the KLD’s order of initialization within the sub subsystem. Valid values for this argument are defined in the sysinit_elem_order enumeration, found in <sys/kernel.h>.

enum sysinit_elem_order {
        SI_ORDER_FIRST          = 0x0000000,    /* First.               */
        SI_ORDER_SECOND         = 0x0000001,    /* Second.              */
        SI_ORDER_THIRD          = 0x0000002,    /* Third.               */
        SI_ORDER_FOURTH         = 0x0000003,    /* Fourth.              */
      SI_ORDER_MIDDLE         = 0x1000000,    /* Somewhere in the middle. */
        SI_ORDER_ANY            = 0xfffffff     /* Last.                    */
};

In general, we’ll always set order to SI_ORDER_MIDDLE.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.237.29