Linux Notification chain學習

概 述
    內核許多子系統之間關聯緊密,因此在一個子系統發生或者檢測到的事件信息很可能對其他子系統來說也是有價值的。爲了滿足其他子系統對這些事件信息的需求,即在某個子系統內發生或檢測到事件時,其他對此感興趣的子系統也能知道事件的發生,內核提供了notification chain機制。

注意:notification chain適用於內核子系統之間的信息傳遞,不涉及用戶態。

Notification chain使用發佈-訂閱模型(publish-and-subscribe model)
    在事件發生時,檢測或產生事件的子系統作爲主動一方通過通知函數來告知作爲被動一方的訂閱者(對此事件感興趣的子系統)。這裏有個額外要求,訂閱一方要提供callback函數以供發佈方調用,當然,提供什麼樣的callback函數完全由訂閱方決定。訂閱者必須知道其他子系統提供了哪些事件通知支持,以選擇可以訂閱的事件通知;當然,訂
閱者本身也是一個子系統,因此也具有信息發佈功能,因此它也要清楚本系統內哪些事件對其他子系統是有價值的,即有哪些本系統內的事件發生時需要通知訂閱者,但是子系統對誰訂閱了事件通知以及爲什麼要訂閱一無所知。
從某種意義上來說,notification chain實現了事件信息的共享

struct notifier_block結構

Notification chain由notifier block組成,其結構類型如下(include/linux/notifier.h):
struct notifier_block
{
       int (*notifier_call)(struct notifier_block *self, unsigned long, void *);
       struct notifier_block *next;
       int priority;
};
notifier_call回調函數:self參數通常爲notifier_block本身;unsigned long型參數表示發生的事件類型,因爲一個chain可能支持多個事件,此參數用來對事件進行區分,在include/linux/notifier.h文件中預訂了一些事件常量,例如與netdevice相關的就有多個事件;void *用來存放私有信息,其具體信息取決於特定的事件next指針:用於同一個chain中的notifier_block的鏈接priority:表示notifier_call函數的優先級,在事件發生時先調用高優先級的回調函數。實際
上在chain中notifier block是根據優先級來進行排隊的,高優先級的在前面,這樣就可以容易地實現根據優先級來調用回調函數。同優先級的根據加入chain的順序來排隊,最新加入的排在同優先級的最後。通常該字段取缺省值0,這樣回調函數就根據加入chain的順序來調用。該字段的用法可參考函數notifier_chain_register的實現。

實際上notification chain就是一組函數列表。通常notification chain的名字的格式爲xxx_chain、xxx_notifier_chain、 xxx_notifier_list,例如reboot_notifier_list。

回調函數notifier_call
int (*notifier_call)(struct notifier_block *self, unsigned long, void *);
前面已經講過函數參數的含義,注意到該函數的返回值爲int類型,這裏就來看一下可能的返回
值(include/linux/notifier.h文件中定義了這些常值):
NOTIFY_DONE              0x0000                                                       對該事件不感興趣(根據unsigned long參數)
NOTIFY_OK                    0x0001                                                       成功響應該事件
NOTIFY_STOP_MASK  0x8000                                                        該回調函數返回後停止處理後續notifier block
NOTIFY_BAD                 (NOTIFY_STOP_MASK|0x0002)             出錯,回調函數返回後停止處理後續notifier block
NOTIFY_STOP              (NOTIFY_OK|NOTIFY_STOP_MASK)     成功響應事件,回調函數返回後停止處理後續notifier block

注意,NOTIFY_STOP和NOTIFY_BAD的定義都包含了NOTIFY_STOP_MASK。

併發訪問控制在kernel/sys.c文件中定義了對notification chain進行併發訪問控制的讀寫鎖notifier_lock。系統中對所有notification chain的併發訪問都是由該鎖來控制。子系統通常只在boot或者加載module時註冊notifier block,即修改notification chain,而大多數時間僅以只讀方式來訪問,因此一個鎖基本不會影響系統性能。

 基 本 API

下面的基本例程位於kernel/sys.c文件中要接收某些事件的通知需要先註冊到支持這些事件的notification chain中:
int notifier_chain_register(struct notifier_block **list, struct notifier_block *n)

list爲notification chain
n爲當前子系統提供的notifier_block,其中指明瞭回調函數該函數會根據notifier_block的優先級priority將n插入到list中合適位置如果不想接受已訂閱事件的通知,則需要取消訂閱註冊:
int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n)
nl爲notification chain
n爲當前子系統提供的notifier_block當事件發生時,要通知訂閱該事件的子系統:
int notifier_call_chain(struct notifier_block **n, unsigned long val, void *v)
n爲notification chain
val爲事件類型,前面提到過一個chain可能支持多個事件,該參數用來對事件進行區分
v存放特定於事件的信息該函數會遍歷chain,對chain中每個notifier block,以參數val和v調用其notifier_call函數,若notifier_call函數返回值中標誌了NOTIFY_STOP_MASK(如NOTIFY_BAD、NOTIFY_STOP),則函數停止處理,返回當前notifier block的返回值;否則返回chain中最後一個notifier block的返回值。

注意:多數子系統都定義了這些基本例程的封裝函數,因此很少看到對這些函數的直接調用。例如後面的例子中用到的register_reboot_notifier和unregister_reboot_notifier就是簡單的封裝函數。
 
 簡 單 示 例

    在kernel/sys.c文件中定義了一個全局reboot_notifier_list,該chain用來掛接想在系統shutdown時執行的函數,例如進行某種清理工作。前面提到過register_reboot_notifier和unregister_reboot_notifier是對notifier_chain_register和notifier_chain_unregister簡單封裝,具體請參考實現,在此不再贅述。下面給出一個簡單的使用notification chain的例子。該模塊向reboot_notifier_list註冊了一個函數myreboot,該函數在系統reboot時會簡單地打印一些信息(在系統shutdown時也可以)注意到傳遞給myreboot的event參數值爲1,而include/linux/notifier.h文件中定義了與系統關機相關的一組事件常值,而1對應於事件SYS_DOWN。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/notifier.h>

#include <linux/reboot.h>
static int myreboot(struct notifier_block *self, unsigned long event, void *data)
{
    printk(KERN_ALERT "Just a test! Event code: %li! System reboot now...", event);
    return NOTIFY_OK;
}

static struct notifier_block myreboot_notifier = {
    .notifier_call = myreboot,
};

static int myreboot_init(void)
{
    register_reboot_notifier(&myreboot_notifier);
    return 0;
}

static void myreboot_exit(void)
{
    unregister_reboot_notifier(&myreboot_notifier);
}

module_init(myreboot_init);
module_exit(myreboot_exit);

MODULE_AUTHOR("dj");
MODULE_DESCRIPTION("just a notify test!");
MODULE_LICENSE("Dual BSD/GPL");


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章