天天看点

《Essential Linux Device Drivers》第3章(下)

清单3.5 使用工作队列进行延后工作

#include <linux/workqueue.h>

struct workqueue_struct *wq;

/* Driver Initialization */

static int __init

mydrv_init(void)

{

  /* ... */

  wq = create_singlethread_workqueue("mydrv");

  return 0;

}

  /* Work Submission. The first argument is the work function, and

     the second argument is the argument to the work function */

  int

  submit_work(void (*func)(void *data), void *data)

  {

    struct work_struct *hardwork;

    hardwork = kmalloc(sizeof(struct work_struct), GFP_KERNEL);

    /* Init the work structure */

    INIT_WORK(hardwork, func, data);

    /* Enqueue Work */

    queue_work(wq, hardwork);

    return 0;

EXPORT_SYMBOL_GPL(queue_work);

    下列语句可用于宣布你的模块使用GPL copyleft:

MODULE_LICENSE("GPL");

通知链

通知链(Notifier chains)可用于将状态改变信息发送给请求这些改变的代码段。与硬编码不同,notifier提供了一种在感兴趣的事件产生时获得警告的技术。Notifier的初始目的是将网络事件传递给内核中感兴趣的部分,但是现在也可用于许多其他目的。内核已经为主要的事件预先定义了notifier。这样的通知的实例包括:

(1)死亡通知。当内核触发了一个陷阱和错误(由oops、缺页或断点命中引发)时被发送。例如,如果你正在为一个医疗等级卡编写设备驱动,你可能需要注册自身接受死亡通知,这样,当内核恐慌发生时,你可以关闭医疗电子。

(2)网络设备通知。当一个网络接口卡启动和关闭的时候被发送。

(4)Internet地址通知。当侦测到网络接口卡的IP地址发送改变的时候,会发送此通知。

Notifier的应用实例是drivers/net/wan/hdlc.c中的高级数据链路控制(HDLC)协议驱动,它会注册自己到网络设备通知链,以侦测载波状态的改变。

为了将你的代码与某通知链关联,你必须注册一个相关链的时间处理函数。当相应的事件发生时,事件ID和与通知相关的参数会传递给该处理函数。为了实现一个自定义的通知链,你必须另外实现底层结构,以便当事件被侦测到时,链会被激活。

表3.2 通知链和它们传送的事件

描述

Die Notifier Chain(die_chain)

通过register_die_notifier(),my_die_event_handler()被依附于die_chain死亡通知链。为了触发my_die_event_handler()的发生,代码中引入了一个冗余的引用,即:

死亡事件通知将die_args结构体传给被注册的事件处理函数。该参数包括一个指向regs结构体的指针(在发生缺陷的时候,用于存放处理器的寄存器)。my_die_event_handler()中打印了指令指针寄存器的内容。

通过register_netdevice_notifier(),my_dev_event_handler()被依附于网络设备通知链netdev_chain。通过改变网络接口设备(如以太网ethX和回环设备lo)的状态可以产生此事件:

它会导致my_dev_event_handler()的执行。

net_device结构体的指针被传给该处理函数作为参数,它包含了网络接口的名字,my_dev_event_handler()打印出了该信息:

Val=1意味着NETDEV_UP事件,其定义在include/linux/notifier.h文件中。

User-Defined Notifier Chain

清单3.6也实现了一个用户自定义的通知链my_noti_chain。假定你希望当用户读取proc文件系统中一个特定的文件的时候该事件被产生,可以在相关的procfs读函数中加入如下代码:

当你读取相应的/proc文件时,my_event_handler()将被调用,如下信息被打印出来:

Val包含了产生事件的ID,本例中为100。该函数的参数没有被使用。

清单3.6 通知事件处理函数

#include <linux/notifier.h>

#include <asm/kdebug.h>

#include <linux/netdevice.h>

#include <linux/inetdevice.h>

/* Die Notifier Definition */

static struct notifier_block my_die_notifier = {

  .notifier_call = my_die_event_handler,

};

/* Die notification event handler */

int

my_die_event_handler(struct notifier_block *self,

                          unsigned long val, void *data)

  struct die_args *args = (struct die_args *)data;

  if (val == 1) { /* '1' corresponds to an "oops" */

    printk("my_die_event: OOPs! at EIP=%lx\n", args->regs->eip);

  } /* else ignore */

/* Net Device notifier definition */

static struct notifier_block my_dev_notifier = {

  .notifier_call = my_dev_event_handler,

/* Net Device notification event handler */

int my_dev_event_handler(struct notifier_block *self,

                              unsigned long val, void *data)

  printk("my_dev_event: Val=%ld, Interface=%s\n", val,

           ((struct net_device *) data)->name);

/* User-defined notifier chain implementation */

static BLOCKING_NOTIFIER_HEAD(my_noti_chain);

static struct notifier_block my_notifier = {

  .notifier_call = my_event_handler,

/* User-defined notification event handler */

int my_event_handler(struct notifier_block *self,

  printk("my_event: Val=%ld\n", val);

my_init(void)

  /* Register Die Notifier */

  register_die_notifier(&my_die_notifier);

  /* Register Net Device Notifier */

  register_netdevice_notifier(&my_dev_notifier);

  /* Register a user-defined Notifier */

  blocking_notifier_chain_register(&my_noti_chain, &my_notifier);

通过BLOCKING_NOTIFIER_HEAD(),清单3.6中的my_noti_chain被定义为一个阻塞通知,经由对blocking_notifier_chain_register()函数的调用,它被注册。这意味着该通知事件处理函数总是在进程上下文被调用,也允许睡眠。如果你的通知处理函数允许从中断上下文调用,你应该使用ATOMIC_NOTIFIER_HEAD()定义该通知链并使用atomic_notifier_chain_register()注册它。

老的通知接口

完成接口

一些使用场景的例子包括:

(1)你的驱动模块中包含了一个辅助内核线程。当你卸载这个模块时,在模块的代码从内核空间被移除之前,release()函数将被调用。release函数中要求内核线程杀死自身,它一直阻塞等待线程的退出。清单3.7实现了这个例子。

(2)你正在编写块设备驱动(第14章《块设备驱动》讨论)中将设备读请求排队的部分。这激活了以单独线程或工作队列方式实现的一个状态机的变更,而驱动本身想一直等到该操作完成前才执行下一次操作。drivers/block/floppy.c就是这样的一个例子。

(3)一个应用请求模拟/数字转换(ADC)驱动完成一次数据采样。该驱动初始化一个转换请求,接下来一直等待转换完成的中断产生,并返回转换后的数据。

static DECLARE_COMPLETION(my_thread_exit);      /* Completion */

static DECLARE_WAIT_QUEUE_HEAD(my_thread_wait); /* Wait Queue */

int pink_slip = 0;                              /* Exit Flag */

/* Helper thread */

static int

my_thread(void *unused)

  DECLARE_WAITQUEUE(wait, current);

  daemonize("my_thread");

  add_wait_queue(&my_thread_wait, &wait);

  while (1) {

    /* Relinquish processor until event occurs */

    set_current_state(TASK_INTERRUPTIBLE);

    schedule();

    /* Control gets here when the thread is woken

       up from the my_thread_wait wait queue */

    /* Quit if let go */

    if (pink_slip) {

      break;

    }

    /* Do the real work */

    /* ... */

  }

  /* Bail out of the wait queue */

  __set_current_state(TASK_RUNNING);

  remove_wait_queue(&my_thread_wait, &wait);

  /* Atomically signal completion and exit */

  complete_and_exit(&my_thread_exit, 0);

/* Module Initialization */

  /* Kick start the thread */

  kernel_thread(my_thread, NULL,

                CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD);

/* Module Release */

static void __exit

my_release(void)

  pink_slip = 1;                        /* my_thread must go */

  wake_up(&my_thread_wait);             /* Activate my_thread */

  wait_for_completion(&my_thread_exit); /* Wait until my_thread

                                           quits */

在清单3.7中的my_release()函数中,在唤醒my_thread()之前,它通过pink_slip设置了一个退出请求标志。接下来,它调用wait_for_completion()等待my_thread()完成其退出。my_thread()函数醒来后,发现pink_slip被设置,它进行如下工作:

(1)向my_release()函数通知完成;

(2)杀死自身

在第11章中,开发一个遥测设备驱动的时候,我们会使用完成接口。

Kthread为原始的线程创建函数添加了一层外衣由此简化了线程管理的任务。

清单3.8使用kthread接口重写了清单3.7。my_init()现在调用kthread_create()而不是kernel_thread(),你可以将线程的名字传入kthread_create(),而不再需要明确地在线程内调用daemonize()。

Kthread允许你自由地调用内建的由完成接口所实现的退出同步机制。因此,如清单3.8中my_release()函数所为,你可以直接调用kthread_stop()而不再需要设置pink_slip、唤醒my_thread()并使用wait_for_completion()等待它的完成。相似地,my_thread()可以进行一个简洁的对kthread_should_stop()的调用以确认其是否应该退出。

/* '+' and '-' show the differences from Listing 3.7 */

#include <linux/kthread.h>

/* Assistant Thread */

   DECLARE_WAITQUEUE(wait, current);

-   daemonize("my_thread");

-   while (1) {

+   /* Continue work if no other thread has

+    * invoked kthread_stop() */

+   while (!kthread_should_stop()) {

      /* ... */

-     /* Quit if let go */

-     if (pink_slip) {

-       break;

-     }

    __set_current_state(TASK_RUNNING);

    remove_wait_queue(&my_thread_wait, &wait);

-   complete_and_exit(&my_thread_exit, 0);

+   return 0;

 }

+   struct task_struct *my_task;

 /* Module Initialization */

 static int __init

 my_init(void)

 {

-   kernel_thread(my_thread, NULL,

-                 CLONE_FS | CLONE_FILES | CLONE_SIGHAND |

                  SIGCHLD);

+   my_task = kthread_create(my_thread, NULL, "%s", "my_thread");

+   if (my_task) wake_up_process(my_task);

 /* Module Release */

 static void __exit

 my_release(void)

-   pink_slip = 1;

-   wake_up(&my_thread_wait);

-   wait_for_completion(&my_thread_exit);

+   kthread_stop(my_task);

kthread_run(my_thread, NULL, "%s", "my_thread");

错误处理助手

#include <linux/err.h>

char *

collect_data(char *userbuffer)

  char *buffer;

  buffer = kmalloc(100, GFP_KERNEL);

  if (!buffer) { /* Out of memory */

    return ERR_PTR(-ENOMEM);

  if (copy_from_user(buffer, userbuffer, 100)) {

    return ERR_PTR(-EFAULT);

  return(buffer);

my_function(char *userbuffer)

  char *buf;

  buf = collect_data(userbuffer);

  if (IS_ERR(buf)) {

    printk("Error returned is %d!\n", PTR_ERR(buf));

Error returned is -12!

但是,如果collect_data()执行成功,它将返回一个数据缓冲区的指针。

再来一个例子,我们给清单3.8中的线程创建代码添加错误处理(使用IS_ERR()和PTR_ERR()):

my_task = kthread_create(my_thread, NULL, "%s", "mydrv");

+  if (!IS_ERR(my_task)) {

+    /* Success */

     wake_up_process(my_task);

+  } else {

+    /* Failure */

+    printk("Error value returned=%d\n", PTR_ERR(my_task));

+  }

查看源代码

ksoftirqd、pdflush和 khubd内核线程代码分别在kernel/softirq.c, mm/pdflush.c和 drivers/usb/core/hub.c文件中。

kernel/exit.c可以找到daemonize(),以用户模式助手的实现见于kernel/kmod.c文件。

内核工作队列的实现位于kernel/workqueue.c文件,为了理解工作队列的用法,可以查看drivers/net/wireless/ipw2200.c中PRO/Wireless 2200网卡驱动。

内核通知链的实现位于kernel/sys.c和include/linux/notifier.h文件。查看kernel/sched.c和include/linux/completion.h文件可以挖掘完成接口的实现机理。kernel/kthread.c包含了kthread辅助接口的源代码,include/linux/err.h则包含了错误处理接口的定义。

表3.3给出了本章中所使用的主要的数据结构及其源代码路径的总结。表3.4列出了本章中使用的主要内核编程接口及其源代码路径。

数据结构

路径

wait_queue_t

include/linux/wait.h

list_head

include/linux/list.h

hlist_head

用于实现哈希表的的内核结构体

work_struct

include/linux/workqueue.h

notifier_block

include/linux/notifier.h

completion

include/linux/completion.h

DECLARE_WAITQUEUE()

定义一个等待队列

add_wait_queue()

kernel/wait.c

remove_wait_queue()

wake_up_interruptible()

include/linux/wait.h kernel/sched.c

schedule()

kernel/sched.c

set_current_state()

include/linux/sched.h

kernel_thread()

arch/your-arch/kernel/process.c

daemonize()

kernel/exit.c

allow_signal()

使能某指定信号的发起

signal_pending()

call_usermodehelper()

include/linux/kmod.h kernel/kmod.c

执行一个用户模式的程序

Linked list library functions

看表3.1

register_die_notifier()

arch/your-arch/kernel/traps.c

注册一个die通知

register_netdevice_notifier()

net/core/dev.c

注册一个netdevice通知

register_inetaddr_notifier()

net/ipv4/devinet.c

注册一个inetaddr通知

BLOCKING_NOTIFIER_HEAD()

创建一个用户自定义的阻塞性的通知

blocking_notifier_chain_register()

kernel/sys.c

注册一个阻塞性的通知

blocking_notifier_call_chain()

将事件分发给一个阻塞性的通知链

ATOMIC_NOTIFIER_HEAD()

创建一个原子性的通知

atomic_notifier_chain_register()

注册一个原子性的通知

DECLARE_COMPLETION()

静态定义一个完成实例

init_completion()

动态定义一个完成实例

complete()

宣布完成

wait_for_completion()

一直等待完成实例的完成

complete_and_exit()

原子性的通知完成并退出

kthread_create()

kernel/kthread.c

创建一个内核线程

kthread_stop()

让一个内核线程停止

kthread_should_stop()

IS_ERR()

include/linux/err.h

 本文转自 21cnbao 51CTO博客,原文链接:http://blog.51cto.com/21cnbao/120805,如需转载请自行联系原作者

继续阅读