设备驱动设计模式

本文档描述了设备驱动程序中常见的一些设计模式。子系统维护者可能会要求驱动程序开发人员遵循这些设计模式。

  1. 状态容器

  2. container_of()

1. 状态容器

虽然内核包含一些设备驱动程序,它们假设在特定系统(单例)上只会探测 (probe()) 一次,但通常假设驱动程序绑定的设备会以多个实例出现。 这意味着 probe() 函数和所有回调需要是可重入的。

实现此目的的最常见方法是使用状态容器设计模式。 它通常具有以下形式

struct foo {
    spinlock_t lock; /* Example member */
    (...)
};

static int foo_probe(...)
{
    struct foo *foo;

    foo = devm_kzalloc(dev, sizeof(*foo), GFP_KERNEL);
    if (!foo)
        return -ENOMEM;
    spin_lock_init(&foo->lock);
    (...)
}

这将在每次调用 probe() 时在内存中创建一个 struct foo 的实例。 这是此设备驱动程序实例的状态容器。 当然,有必要始终将此状态实例传递给所有需要访问该状态及其成员的函数。

例如,如果驱动程序正在注册中断处理程序,您将像这样传递一个指向 struct foo 的指针

static irqreturn_t foo_handler(int irq, void *arg)
{
    struct foo *foo = arg;
    (...)
}

static int foo_probe(...)
{
    struct foo *foo;

    (...)
    ret = request_irq(irq, foo_handler, 0, "foo", foo);
}

这样,您始终可以在中断处理程序中获得指向 foo 的正确实例的指针。

2. container_of()

继续上面的例子,我们添加一个卸载的工作

struct foo {
    spinlock_t lock;
    struct workqueue_struct *wq;
    struct work_struct offload;
    (...)
};

static void foo_work(struct work_struct *work)
{
    struct foo *foo = container_of(work, struct foo, offload);

    (...)
}

static irqreturn_t foo_handler(int irq, void *arg)
{
    struct foo *foo = arg;

    queue_work(foo->wq, &foo->offload);
    (...)
}

static int foo_probe(...)
{
    struct foo *foo;

    foo->wq = create_singlethread_workqueue("foo-wq");
    INIT_WORK(&foo->offload, foo_work);
    (...)
}

对于 hrtimer 或类似的东西,设计模式是相同的,它将返回一个单参数,该参数是指向回调中某个 struct 成员的指针。

container_of() 是在 <linux/kernel.h> 中定义的宏

container_of() 的作用是通过使用标准 C 中的 offsetof() 宏从成员的指针获取指向包含 struct 的指针,方法是进行简单的减法,这允许类似于面向对象的行为。 请注意,包含的成员不能是指针,而必须是实际成员才能使其工作。

我们可以在这里看到,我们避免了以这种方式拥有指向我们的 struct foo * 实例的全局指针,同时仍然保持传递给工作函数的参数数量为单个指针。