概述
在linux內核中,各個子系統之間有很強的相互關系,某些子系統可能對其它子系統產生的事件感興趣。為了讓某個子系統在發生某個事件時通知感興趣的子系統,Linux內核引入了通知鏈技術。通知鏈只能夠在內核的子系統之間使用,而不能夠在內核和用戶空間進行事件的通知。
組成內核的核心系統代碼均位于kernel目錄下,通知鏈表就位于其中,它位于 kernel/notifier.c 中,對應的頭文件為 include/linux/notifier.h 。
從技術上來講,這并不是一個多么復雜、高深、難懂的部分,說白了就是一個單向鏈表的插入、刪除和遍歷等操作。實現她的代碼不超過1000行。
數據結構
所有通知鏈的核心數據結構都位于 notifier.h 中。通知鏈的核心結構是 notifier_block 。
struct notifier_block { notifier_fn_t notifier_call; struct notifier_block __rcu *next; int priority; };
其中 notifier_call 是通知鏈要執行的函數指針, next 用來連接其它的通知結構, priority 是這個通知的優先級,同一條鏈上的 notifier_block 是按優先級排列的,數字越大,優先級越高,越會被先執行。
內核代碼中一般把通知鏈命名為 xxx_chain , xxx_nofitier_chain 這種形式的變量名。圍繞核心數據結構 notifier_block ,內核定義了四種通知鏈類型,它們的主要區別就是在執行通知鏈上的回調函數時是否有安全保護措施:
1. 原子通知鏈( Atomic notifier chains ):原子通知鏈采用的自旋鎖,通知鏈元素的回調函數(當事件發生時要執行的函數)在中斷或原子操作上下文中運行,不允許阻塞。對應的鏈表頭結構 :
struct atomic_notifier_head { spinlock_t lock; struct notifier_block __rcu *head; };
2. 可阻塞通知鏈( Blocking notifier chains ):可阻塞通知鏈使用信號量實現回調函數的加鎖,通知鏈元素的回調函數在進程上下文中運行,允許阻塞。對應的鏈表頭 :
struct blocking_notifier_head { struct rw_semaphore rwsem; struct notifier_block __rcu *head; };
3. 原始通知鏈( Raw notifier chains ):對通知鏈元素的回調函數沒有任何限制,所有鎖和保護機制都由調用者維護。對應的鏈表頭 :
struct raw_notifier_head { struct notifier_block __rcu *head; };
4. SRCU 通知鏈( SRCU notifier chains ):可阻塞通知鏈的一種變體,采用互斥鎖和叫做 可睡眠的讀拷貝更新機制 (Sleepable Read-Copy UpdateSleepable Read-Copy Update)。對應的鏈表頭 :
struct srcu_notifier_head { struct mutex mutex; struct srcu_struct srcu; struct notifier_block __rcu *head; };
如何使用通知鏈
這四類通知鏈,我們該怎么用這才是我需要關心的問題。在定義自己的通知鏈的時候,心里必須明確,自己需要一個什么樣類型的通知鏈,是原子的、可阻塞的還是一個原始通知鏈。內核中用于定義并初始化不同類通知鏈的函數分別是 :
#define ATOMIC_NOTIFIER_HEAD(name) struct atomic_notifier_head name = ATOMIC_NOTIFIER_INIT(name) #define BLOCKING_NOTIFIER_HEAD(name) struct blocking_notifier_head name = BLOCKING_NOTIFIER_INIT(name) #define RAW_NOTIFIER_HEAD(name) struct raw_notifier_head name = RAW_NOTIFIER_INIT(name)
其實, ATOMIC_NOTIFIER_HEAD(mynofifierlist) 和下面的代碼是等價的,展開之后如下:
struct atomic_notifier_head mynofifierlist = { .lock = __SPIN_LOCK_UNLOCKED(mynofifierlist.lock), .head = NULL }
另外兩個接口也類似。如果我們已經有一個通知鏈的對象,Linux還提供了一組用于初始化一個通知鏈對象的API:
#define ATOMIC_INIT_NOTIFIER_HEAD(name) do { spin_lock_init(&(name)->lock); (name)->head = NULL; } while (0) #define BLOCKING_INIT_NOTIFIER_HEAD(name) do { init_rwsem(&(name)->rwsem); (name)->head = NULL; } while (0) #define RAW_INIT_NOTIFIER_HEAD(name) do { (name)->head = NULL; } while (0)
這一組接口一般在下列格式的代碼里見到的會比較多一點:
static struct atomic_notifier_head dock_notifier_list; ATOMIC_INIT_NOTIFIER_HEAD(&dock_notifier_list);
有了通知鏈只是第一步,接下來我們還需要提供往通知鏈上注冊通知塊、卸載通知塊、已經遍歷執行通知鏈上每個通知塊里回調函數的基本接口,說白了就是單向鏈表的插入、刪除和遍歷,這樣理解就可以了。
內核提供最基本的通知鏈的常用接口為如下:
static int notifier_chain_register(struct notifier_block **nl, struct notifier_block *n) static int notifier_chain_cond_register(struct notifier_block **nl, struct notifier_block *n) static int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n) static int notifier_call_chain(struct notifier_block **nl, unsigned long val, void *v, int nr_to_call, int *nr_calls)
這最基本的三個接口分別實現了對通知鏈上通知塊的注冊、卸載和遍歷操作,可以想象,原子通知鏈、可阻塞通知鏈和原始通知鏈一定會對基本通知鏈的操作函數再進行一次包裝的,事實也確實如此:
// 注冊函數 extern int atomic_notifier_chain_register(struct atomic_notifier_head *nh, struct notifier_block *nb); extern int blocking_notifier_chain_register(struct blocking_notifier_head *nh, struct notifier_block *nb); extern int raw_notifier_chain_register(struct raw_notifier_head *nh, struct notifier_block *nb); extern int srcu_notifier_chain_register(struct srcu_notifier_head *nh, struct notifier_block *nb); extern int blocking_notifier_chain_cond_register( struct blocking_notifier_head *nh, struct notifier_block *nb); // 卸載函數 extern int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh, struct notifier_block *nb); extern int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh, struct notifier_block *nb); extern int raw_notifier_chain_unregister(struct raw_notifier_head *nh, struct notifier_block *nb); extern int srcu_notifier_chain_unregister(struct srcu_notifier_head *nh, struct notifier_block *nb); // 遍歷操作 extern int atomic_notifier_call_chain(struct atomic_notifier_head *nh, unsigned long val, void *v); extern int __atomic_notifier_call_chain(struct atomic_notifier_head *nh, unsigned long val, void *v, int nr_to_call, int *nr_calls); extern int blocking_notifier_call_chain(struct blocking_notifier_head *nh, unsigned long val, void *v); extern int __blocking_notifier_call_chain(struct blocking_notifier_head *nh, unsigned long val, void *v, int nr_to_call, int *nr_calls); extern int raw_notifier_call_chain(struct raw_notifier_head *nh, unsigned long val, void *v); extern int __raw_notifier_call_chain(struct raw_notifier_head *nh, unsigned long val, void *v, int nr_to_call, int *nr_calls); extern int srcu_notifier_call_chain(struct srcu_notifier_head *nh, unsigned long val, void *v); extern int __srcu_notifier_call_chain(struct srcu_notifier_head *nh, unsigned long val, void *v, int nr_to_call, int *nr_calls);
上述這四類通知鏈的基本API又構成了內核中其他子系統定義、操作自己通知鏈的基礎。例如,Netlink定義了一個原子通知鏈,所以,它對原子通知鏈的基本API又封裝了一層,以形成自己的特色:
static ATOMIC_NOTIFIER_HEAD(netlink_chain); int netlink_register_notifier(struct notifier_block *nb) { return atomic_notifier_chain_register(&netlink_chain, nb); } EXPORT_SYMBOL(netlink_register_notifier); int netlink_unregister_notifier(struct notifier_block *nb) { return atomic_notifier_chain_unregister(&netlink_chain, nb); } EXPORT_SYMBOL(netlink_unregister_notifier);
網絡事件也有一個原子通知鏈(net/core/netevent.c):
/* * Network event notifiers * * Authors: * Tom Tucker <tom@opengridcomputing.com> * Steve Wise <swise@opengridcomputing.com> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * * Fixes: */ #include <linux/rtnetlink.h> #include <linux/notifier.h> #include <linux/export.h> #include <net/netevent.h> static ATOMIC_NOTIFIER_HEAD(netevent_notif_chain); /** * register_netevent_notifier - register a netevent notifier block * @nb: notifier * * Register a notifier to be called when a netevent occurs. * The notifier passed is linked into the kernel structures and must * not be reused until it has been unregistered. A negative errno code * is returned on a failure. */ int register_netevent_notifier(struct notifier_block *nb) { int err; err = atomic_notifier_chain_register(&netevent_notif_chain, nb); return err; } EXPORT_SYMBOL_GPL(register_netevent_notifier); /** * netevent_unregister_notifier - unregister a netevent notifier block * @nb: notifier * * Unregister a notifier previously registered by * register_neigh_notifier(). The notifier is unlinked into the * kernel structures and may then be reused. A negative errno code * is returned on a failure. */ int unregister_netevent_notifier(struct notifier_block *nb) { return atomic_notifier_chain_unregister(&netevent_notif_chain, nb); } EXPORT_SYMBOL_GPL(unregister_netevent_notifier); /** * call_netevent_notifiers - call all netevent notifier blocks * @val: value passed unmodified to notifier function * @v: pointer passed unmodified to notifier function * * Call all neighbour notifier blocks. Parameters and return value * are as for notifier_call_chain(). */ int call_netevent_notifiers(unsigned long val, void *v) { return atomic_notifier_call_chain(&netevent_notif_chain, val, v); } EXPORT_SYMBOL_GPL(call_netevent_notifiers);
運作機制
通知鏈的運作機制包括兩個角色:
- 被通知者:對某一事件感興趣一方。定義了當事件發生時,相應的處理函數,即回調函數,被通知者將其注冊到通知鏈中(被通知者注冊的動作就是在通知鏈中增加一項)。
- 通知者:事件的通知者。當檢測到某事件,或者本身產生事件時,通知所有對該事件感興趣的一方事件發生。它定義了一個通知鏈,其中保存了每一個被通知者對事件的回調函數。通知這個過程實際上就是遍歷通知鏈中的每一項,然后調用相應的回調函數。
包括以下過程:
- 通知者定義通知鏈。
- 被通知者向通知鏈中注冊回調函數。
- 當事件發生時,通知者發出通知(執行通知鏈中所有元素的回調函數)。
其他注意事項
1. 如果一個子系統A在運行過程中會產生一個實時事件,而這些事件對其他子系統來說非常重要,那么子系統A可以定義一個自己的通知鏈對象,根據需求可以選擇原子通知鏈、非阻塞通知鏈和原始通知鏈,并向外提供向這個通知鏈里注冊、卸載、執行事件的回調函數的接口。
2. 如果子系統B對子系統A中的某些事件感興趣,或者說強依賴,就是說子系統B需要根據子系統A中某些事件來執行自己特定的操作,那么此時系統B需要實例化一個通知塊 struct notifier_block xxx{} ,然后編寫通知塊里的回調處理函數來相應系統A中的事件就可以了。
3. 通知塊 struct notifier_block xxx{} 里有一個優先級的特性,起始在標準內核里每個實例化的通知塊都沒有使用優先級。不用優先級字段的結果就是:先注冊的通知塊里的回調函數在事件發生時會先執行。注意這里說的后注冊指的是模塊被動態加載到內核的先后順序,和哪個模塊代碼先寫沒有關系。
注意區分。意思就是說,如果子系統B和C都對子系統A的up事件感興趣,B和C在向A注冊up事件的回調函數時并沒有指定函數的優先級。無論是通過`insmod`手動加載模塊B和C,還是系統 boot 時自動加載B和C,哪個模塊先被加載,它的回調函數在A系統的up事件發生時會先被執行
4. 關于通知鏈的回調函數,正常情況下都需要返回 NOTIFY_OK 或者 NOTIFY_DONE ,這樣通知鏈上后面掛載的其他函數可以繼續執行。如果返回 NOTIFY_STOP ,則會使得通知鏈上后續掛載的函數無法得到執行,除非特別想這么做,否則編寫通知鏈回調函數時,最好不要返回這個值。
5. 通知鏈上的回調函數的原型為:
typedef int (*notifier_fn_t)(struct notifier_block *nb, unsigned long action, void *data); struct notifier_block { notifier_fn_t notifier_call; struct notifier_block __rcu *next; int priority; };
其中第二個參數一般用于指明事件的類型。通知都是一個整數;而第三個參數是一個 void 類型的內存地址,在不同的子系統中表示不同的信息。我們在設計自己的通知鏈系統可以用第三個入參實現在通知系統和被通知系統之間數據的傳遞,以便被通知系統的工作可以更加緊湊、高效。
6. 如果以后在看到內核代碼中某個子系統在調用通知鏈注冊函數時,做到以下幾點就沒事了:
- 心里首先要明確,這個注冊通知鏈回調函數的系統一定和提供通知鏈的系統有某種聯系,且本系統需要那個系統對某些重要事件進行響應。
- 看本系統注冊的通知鏈回調函數的實現,具體看它對哪些事件感興趣,并且是怎么處理的。
- 看看提供通知鏈對象的系統有哪些事件;
最后,也就明白了這個子系統為什么要用通知鏈來感知別的系統的變化了,這樣一來,對這兩個子系統從宏觀到微觀的層面上都有一個總體的認識和把握,后續研究起來就順風順水了。
了解更多,Linux相關知識,可以關注我。