天天看點

《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,如需轉載請自行聯系原作者

繼續閱讀