Linux內核中各個子系統相互依賴,當其中某個子系統狀態發生改變時,就必須使用一定的機制告知使用其服務的其他子系統,以便其他子系統採取相應的措施。爲滿足這樣的需求,內核實現了事件通知鏈機制(notificationchain)。
通知鏈只能用在各個子系統之間,而不能在內核和用戶空間進行事件的通知。組成內核的核心繫統代碼均位於kernel目錄下,通知鏈表位於kernel/notifier.c中,對應的頭文件爲include/linux/notifier.h。通知鏈表機制並不複雜,實現它的代碼只有區區幾百行。
事件通知鏈表是一個事件處理函數的列表,每個通知鏈都與某個或某些事件有關,當特定的事件發生時,就調用相應的事件通知鏈中的回調函數,進行相應的處理。
1.2.數據結構
如圖 1中所示,Linux的網絡子系統一共有3個通知鏈:表示ipv4地址發生變化時的inetaddr_chain;表示ipv6地址發生變化的inet6addr_chain;還有表示設備註冊、狀態變化的netdev_chain。
在這些鏈中都是一個個notifier_block結構:
struct notifier_block {
int (*notifier_call)(struct notifier_block *, unsigned long, void *);
struct notifier_block *next;
int priority;
};
其中,
1. notifier_call:當相應事件發生時應該調用的函數,由被通知方提供,如other_subsys_1;
2. notifier_block *next:用於鏈接成鏈表的指針;
3. priority:回調函數的優先級,一般默認爲0。
內核代碼中一般把通知鏈命名爲xxx_chain, xxx_nofitier_chain這種形式的變量名。圍繞核心數據結構notifier_block,內核定義了四種通知鏈類型:
1. 原子通知鏈( Atomic notifier chains ):通知鏈元素的回調函數(當事件發生時要執行的函數)在中斷或原子操作上下文中運行,不允許阻塞。對應的鏈表頭結構:
struct atomic_notifier_head {
spinlock_t lock;
struct notifier_block *head;
};
2. 可阻塞通知鏈( Blocking notifier chains ):通知鏈元素的回調函數在進程上下文中運行,允許阻塞。對應的鏈表頭:
struct blocking_notifier_head {
struct rw_semaphore rwsem;
struct notifier_block *head;
};
3. 原始通知鏈( Raw notifierchains ):對通知鏈元素的回調函數沒有任何限制,所有鎖和保護機制都由調用者維護。對應的鏈表頭:
網絡子系統就是該類型,通過以下宏實現head的初始化
static RAW_NOTIFIER_HEAD(netdev_chain);
#define RAW_NOTIFIER_INIT(name) { \
.head= NULL }
#define RAW_NOTIFIER_HEAD(name) \ //調用他就好了
struct raw_notifier_head name = \
RAW_NOTIFIER_INIT(name)
即:
struct raw_notifier_head netdev_chain = {
.head = NULL;
}
而其回調函數的註冊,比如向netdev_chain的註冊函數:register_netdevice_notifier。
struct raw_notifier_head {
struct notifier_block *head;
};
4. SRCU 通知鏈( SRCU notifier chains ):可阻塞通知鏈的一種變體。對應的鏈表頭:
struct srcu_notifier_head {
struct mutex mutex;
struct srcu_struct srcu;
struct notifier_block *head;
};
1.3. 運行機理
被通知一方(other_subsys_x)通過notifier_chain_register向特定的chain註冊回調函數,並且一般而言特定的子系統會用特定的notifier_chain_register包裝函數來註冊,比如路由子系統使用的是網絡子系統的:register_netdevice_notifier來註冊他的notifier_block。
1.3.1. 向事件通知鏈註冊的步驟
1. 申明struct notifier_block結構
2. 編寫notifier_call函數
3. 調用特定的事件通知鏈的註冊函數,將notifier_block註冊到通知鏈中
如果內核組件需要處理夠某個事件通知鏈上發出的事件通知,其就該在初始化時在該通知鏈上註冊回調函數。
1.3.2. 通知子系統有事件發生
inet_subsys是通過notifier_call_chain來通知其他的子系統(other_subsys_x)的。
notifier_call_chain會按照通知鏈上各成員的優先級順序執行回調函數(notifier_call_x);回調函數的執行現場在notifier_call_chain進程地址空間;其返回值是NOTIFY_XXX的形式,在include/linux/notifier.h中:
#define NOTIFY_DONE 0x0000 /* 對事件視而不見 */
#define NOTIFY_OK 0x0001 /* 事件正確處理 */
#define NOTIFY_STOP_MASK 0x8000 /*由notifier_call_chain檢查,看繼續調用回調函數,還是停止,_BAD和_STOP中包含該標誌 */
#define NOTIFY_BAD (NOTIFY_STOP_MASK|0x0002) /*事件處理出錯,不再繼續調用回調函數 */
/*
*Clean way to return from the notifier and stop further calls.
*/
#define NOTIFY_STOP (NOTIFY_OK|NOTIFY_STOP_MASK) /* 回調出錯,不再繼續調用該事件回調函數 */
notifier_call_chain捕獲並返回最後一個事件處理函數的返回值;注意:notifier_call_chain可能同時被不同的cpu調用,故而調用者必須保證互斥。
1.3.3. 事件列表
對於網絡子系統而言,其事件常以NETDEV_XXX命名;描述了網絡設備狀態(dev->flags)、傳送隊列狀態(dev->state)、設備註冊狀態(dev->reg_state),以及設備的硬件功能特性(dev->features):
include/linux/notifier.h中
/* netdevice notifier chain */
#define NETDEV_UP 0x0001 /* 激活一個網絡設備 */
#define NETDEV_DOWN 0x0002f /* 停止一個網絡設備,所有對該設備的引用都應釋放 */
#define NETDEV_REBOOT 0x0003 /* 檢查到網絡設備接口硬件崩潰,硬件重啓 */
#define NETDEV_CHANGE 0x0004 /* 網絡設備的數據包隊列狀態發生改變 */
#define NETDEV_REGISTER 0x0005 /*一個網絡設備事例註冊到系統中,但尚未激活 */
#define NETDEV_UNREGISTER 0x0006 /*網絡設備驅動已卸載 */
#define NETDEV_CHANGEMTU 0x0007 /*MTU發生了改變 */
#define NETDEV_CHANGEADDR 0x0008 /*硬件地址發生了改變 */
#define NETDEV_GOING_DOWN 0x0009 /*網絡設備即將註銷,有dev->close報告,通知相關子系統處理 */
#define NETDEV_CHANGENAME 0x000A /*網絡設備名改變 */
#define NETDEV_FEAT_CHANGE 0x000B /*feature網絡硬件功能改變 */
#define NETDEV_BONDING_FAILOVER 0x000C /* */
#define NETDEV_PRE_UP 0x000D /* */
#define NETDEV_BONDING_OLDTYPE 0x000E /* */
#define NETDEV_BONDING_NEWTYPE 0x000F /* */