概述
在linux內(nèi)核中,各個(gè)子系統(tǒng)之間有很強(qiáng)的相互關(guān)系,某些子系統(tǒng)可能對(duì)其它子系統(tǒng)產(chǎn)生的事件感興趣。為了讓某個(gè)子系統(tǒng)在發(fā)生某個(gè)事件時(shí)通知感興趣的子系統(tǒng),Linux內(nèi)核引入了通知鏈技術(shù)。通知鏈只能夠在內(nèi)核的子系統(tǒng)之間使用,而不能夠在內(nèi)核和用戶空間進(jìn)行事件的通知。
組成內(nèi)核的核心系統(tǒng)代碼均位于kernel目錄下,通知鏈表就位于其中,它位于 kernel/notifier.c 中,對(duì)應(yīng)的頭文件為 include/linux/notifier.h 。
從技術(shù)上來(lái)講,這并不是一個(gè)多么復(fù)雜、高深、難懂的部分,說(shuō)白了就是一個(gè)單向鏈表的插入、刪除和遍歷等操作。實(shí)現(xiàn)她的代碼不超過(guò)1000行。
數(shù)據(jù)結(jié)構(gòu)
所有通知鏈的核心數(shù)據(jù)結(jié)構(gòu)都位于 notifier.h 中。通知鏈的核心結(jié)構(gòu)是 notifier_block 。
struct notifier_block {
notifier_fn_t notifier_call;
struct notifier_block __rcu *next;
int priority;
};
其中 notifier_call 是通知鏈要執(zhí)行的函數(shù)指針, next 用來(lái)連接其它的通知結(jié)構(gòu), priority 是這個(gè)通知的優(yōu)先級(jí),同一條鏈上的 notifier_block 是按優(yōu)先級(jí)排列的,數(shù)字越大,優(yōu)先級(jí)越高,越會(huì)被先執(zhí)行。
內(nèi)核代碼中一般把通知鏈命名為 xxx_chain , xxx_nofitier_chain 這種形式的變量名。圍繞核心數(shù)據(jù)結(jié)構(gòu) notifier_block ,內(nèi)核定義了四種通知鏈類型,它們的主要區(qū)別就是在執(zhí)行通知鏈上的回調(diào)函數(shù)時(shí)是否有安全保護(hù)措施:
1. 原子通知鏈( Atomic notifier chains ):原子通知鏈采用的自旋鎖,通知鏈元素的回調(diào)函數(shù)(當(dāng)事件發(fā)生時(shí)要執(zhí)行的函數(shù))在中斷或原子操作上下文中運(yùn)行,不允許阻塞。對(duì)應(yīng)的鏈表頭結(jié)構(gòu) :
struct atomic_notifier_head {
spinlock_t lock;
struct notifier_block __rcu *head;
};
2. 可阻塞通知鏈( Blocking notifier chains ):可阻塞通知鏈?zhǔn)褂眯盘?hào)量實(shí)現(xiàn)回調(diào)函數(shù)的加鎖,通知鏈元素的回調(diào)函數(shù)在進(jìn)程上下文中運(yùn)行,允許阻塞。對(duì)應(yīng)的鏈表頭 :
struct blocking_notifier_head {
struct rw_semaphore rwsem;
struct notifier_block __rcu *head;
};
3. 原始通知鏈( Raw notifier chains ):對(duì)通知鏈元素的回調(diào)函數(shù)沒(méi)有任何限制,所有鎖和保護(hù)機(jī)制都由調(diào)用者維護(hù)。對(duì)應(yīng)的鏈表頭 :
struct raw_notifier_head {
struct notifier_block __rcu *head;
};
4. SRCU 通知鏈( SRCU notifier chains ):可阻塞通知鏈的一種變體,采用互斥鎖和叫做 可睡眠的讀拷貝更新機(jī)制 (Sleepable Read-Copy UpdateSleepable Read-Copy Update)。對(duì)應(yīng)的鏈表頭 :
struct srcu_notifier_head {
struct mutex mutex;
struct srcu_struct srcu;
struct notifier_block __rcu *head;
};
如何使用通知鏈
這四類通知鏈,我們?cè)撛趺从眠@才是我需要關(guān)心的問(wèn)題。在定義自己的通知鏈的時(shí)候,心里必須明確,自己需要一個(gè)什么樣類型的通知鏈,是原子的、可阻塞的還是一個(gè)原始通知鏈。內(nèi)核中用于定義并初始化不同類通知鏈的函數(shù)分別是 :
#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)
其實(shí), ATOMIC_NOTIFIER_HEAD(mynofifierlist) 和下面的代碼是等價(jià)的,展開(kāi)之后如下:
struct atomic_notifier_head mynofifierlist =
{
.lock = __SPIN_LOCK_UNLOCKED(mynofifierlist.lock),
.head = NULL
}
另外兩個(gè)接口也類似。如果我們已經(jīng)有一個(gè)通知鏈的對(duì)象,Linux還提供了一組用于初始化一個(gè)通知鏈對(duì)象的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)
這一組接口一般在下列格式的代碼里見(jiàn)到的會(huì)比較多一點(diǎn):
static struct atomic_notifier_head dock_notifier_list; ATOMIC_INIT_NOTIFIER_HEAD(&dock_notifier_list);
有了通知鏈只是第一步,接下來(lái)我們還需要提供往通知鏈上注冊(cè)通知塊、卸載通知塊、已經(jīng)遍歷執(zhí)行通知鏈上每個(gè)通知塊里回調(diào)函數(shù)的基本接口,說(shuō)白了就是單向鏈表的插入、刪除和遍歷,這樣理解就可以了。
內(nèi)核提供最基本的通知鏈的常用接口為如下:
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)
這最基本的三個(gè)接口分別實(shí)現(xiàn)了對(duì)通知鏈上通知塊的注冊(cè)、卸載和遍歷操作,可以想象,原子通知鏈、可阻塞通知鏈和原始通知鏈一定會(huì)對(duì)基本通知鏈的操作函數(shù)再進(jìn)行一次包裝的,事實(shí)也確實(shí)如此:
// 注冊(cè)函數(shù) 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); // 卸載函數(shù) 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又構(gòu)成了內(nèi)核中其他子系統(tǒng)定義、操作自己通知鏈的基礎(chǔ)。例如,Netlink定義了一個(gè)原子通知鏈,所以,它對(duì)原子通知鏈的基本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);
網(wǎng)絡(luò)事件也有一個(gè)原子通知鏈(net/core/netevent.c):
/* * Network event notifiers * * Authors: * Tom Tucker <[email protected]> * Steve Wise <[email protected]> * * 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);
運(yùn)作機(jī)制
通知鏈的運(yùn)作機(jī)制包括兩個(gè)角色:
- 被通知者:對(duì)某一事件感興趣一方。定義了當(dāng)事件發(fā)生時(shí),相應(yīng)的處理函數(shù),即回調(diào)函數(shù),被通知者將其注冊(cè)到通知鏈中(被通知者注冊(cè)的動(dòng)作就是在通知鏈中增加一項(xiàng))。
- 通知者:事件的通知者。當(dāng)檢測(cè)到某事件,或者本身產(chǎn)生事件時(shí),通知所有對(duì)該事件感興趣的一方事件發(fā)生。它定義了一個(gè)通知鏈,其中保存了每一個(gè)被通知者對(duì)事件的回調(diào)函數(shù)。通知這個(gè)過(guò)程實(shí)際上就是遍歷通知鏈中的每一項(xiàng),然后調(diào)用相應(yīng)的回調(diào)函數(shù)。
包括以下過(guò)程:
- 通知者定義通知鏈。
- 被通知者向通知鏈中注冊(cè)回調(diào)函數(shù)。
- 當(dāng)事件發(fā)生時(shí),通知者發(fā)出通知(執(zhí)行通知鏈中所有元素的回調(diào)函數(shù))。
其他注意事項(xiàng)
1. 如果一個(gè)子系統(tǒng)A在運(yùn)行過(guò)程中會(huì)產(chǎn)生一個(gè)實(shí)時(shí)事件,而這些事件對(duì)其他子系統(tǒng)來(lái)說(shuō)非常重要,那么子系統(tǒng)A可以定義一個(gè)自己的通知鏈對(duì)象,根據(jù)需求可以選擇原子通知鏈、非阻塞通知鏈和原始通知鏈,并向外提供向這個(gè)通知鏈里注冊(cè)、卸載、執(zhí)行事件的回調(diào)函數(shù)的接口。
2. 如果子系統(tǒng)B對(duì)子系統(tǒng)A中的某些事件感興趣,或者說(shuō)強(qiáng)依賴,就是說(shuō)子系統(tǒng)B需要根據(jù)子系統(tǒng)A中某些事件來(lái)執(zhí)行自己特定的操作,那么此時(shí)系統(tǒng)B需要實(shí)例化一個(gè)通知塊 struct notifier_block xxx{} ,然后編寫(xiě)通知塊里的回調(diào)處理函數(shù)來(lái)相應(yīng)系統(tǒng)A中的事件就可以了。
3. 通知塊 struct notifier_block xxx{} 里有一個(gè)優(yōu)先級(jí)的特性,起始在標(biāo)準(zhǔn)內(nèi)核里每個(gè)實(shí)例化的通知塊都沒(méi)有使用優(yōu)先級(jí)。不用優(yōu)先級(jí)字段的結(jié)果就是:先注冊(cè)的通知塊里的回調(diào)函數(shù)在事件發(fā)生時(shí)會(huì)先執(zhí)行。注意這里說(shuō)的后注冊(cè)指的是模塊被動(dòng)態(tài)加載到內(nèi)核的先后順序,和哪個(gè)模塊代碼先寫(xiě)沒(méi)有關(guān)系。
注意區(qū)分。意思就是說(shuō),如果子系統(tǒng)B和C都對(duì)子系統(tǒng)A的up事件感興趣,B和C在向A注冊(cè)u(píng)p事件的回調(diào)函數(shù)時(shí)并沒(méi)有指定函數(shù)的優(yōu)先級(jí)。無(wú)論是通過(guò)`insmod`手動(dòng)加載模塊B和C,還是系統(tǒng) boot 時(shí)自動(dòng)加載B和C,哪個(gè)模塊先被加載,它的回調(diào)函數(shù)在A系統(tǒng)的up事件發(fā)生時(shí)會(huì)先被執(zhí)行
4. 關(guān)于通知鏈的回調(diào)函數(shù),正常情況下都需要返回 NOTIFY_OK 或者 NOTIFY_DONE ,這樣通知鏈上后面掛載的其他函數(shù)可以繼續(xù)執(zhí)行。如果返回 NOTIFY_STOP ,則會(huì)使得通知鏈上后續(xù)掛載的函數(shù)無(wú)法得到執(zhí)行,除非特別想這么做,否則編寫(xiě)通知鏈回調(diào)函數(shù)時(shí),最好不要返回這個(gè)值。
5. 通知鏈上的回調(diào)函數(shù)的原型為:
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;
};
其中第二個(gè)參數(shù)一般用于指明事件的類型。通知都是一個(gè)整數(shù);而第三個(gè)參數(shù)是一個(gè) void 類型的內(nèi)存地址,在不同的子系統(tǒng)中表示不同的信息。我們?cè)谠O(shè)計(jì)自己的通知鏈系統(tǒng)可以用第三個(gè)入?yún)?shí)現(xiàn)在通知系統(tǒng)和被通知系統(tǒng)之間數(shù)據(jù)的傳遞,以便被通知系統(tǒng)的工作可以更加緊湊、高效。
6. 如果以后在看到內(nèi)核代碼中某個(gè)子系統(tǒng)在調(diào)用通知鏈注冊(cè)函數(shù)時(shí),做到以下幾點(diǎn)就沒(méi)事了:
- 心里首先要明確,這個(gè)注冊(cè)通知鏈回調(diào)函數(shù)的系統(tǒng)一定和提供通知鏈的系統(tǒng)有某種聯(lián)系,且本系統(tǒng)需要那個(gè)系統(tǒng)對(duì)某些重要事件進(jìn)行響應(yīng)。
- 看本系統(tǒng)注冊(cè)的通知鏈回調(diào)函數(shù)的實(shí)現(xiàn),具體看它對(duì)哪些事件感興趣,并且是怎么處理的。
- 看看提供通知鏈對(duì)象的系統(tǒng)有哪些事件;
最后,也就明白了這個(gè)子系統(tǒng)為什么要用通知鏈來(lái)感知?jiǎng)e的系統(tǒng)的變化了,這樣一來(lái),對(duì)這兩個(gè)子系統(tǒng)從宏觀到微觀的層面上都有一個(gè)總體的認(rèn)識(shí)和把握,后續(xù)研究起來(lái)就順風(fēng)順?biāo)恕?/p>
了解更多,Linux相關(guān)知識(shí),可以關(guān)注我。






