轉載:
http://blog.csdn.net/wuhzossibility/article/details/8079025
http://blog.chinaunix.net/uid-27717694-id-4286337.html
內核通知鏈
1.1. 概述
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 /* */
1.4. 簡單一例:
通過上面所述,notifier_chain機制只能在內核個子系統間使用,因此,這里使用3個模塊:test_notifier_chain_0、 test_notifier_chain_1、test_notifier_chain_2;當 test_notifier_chain_2通過module_init初始化模塊時發出事件TESTCHAIN_2_INIT;然后 test_notifier_chain_1作出相應的處理:打印 test_notifier_chain_2正在初始化。
/* test_chain_0.c :0. 申明一個通知鏈;1. 向內核注冊通知鏈;2. 定義事件; 3. 導出符號,因而必需最后退出*/ #include <linux/notifier.h> #include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> /* printk() */ #include <linux/fs.h> /* everything() */ #define TESTCHAIN_INIT 0x52U static RAW_NOTIFIER_HEAD(test_chain); /* define our own notifier_call_chain */ static int call_test_notifiers(unsigned long val, void *v) { return raw_notifier_call_chain(&test_chain, val, v); } EXPORT_SYMBOL(call_test_notifiers); /* define our own notifier_chain_register func */ static int register_test_notifier(struct notifier_block *nb) { int err; err = raw_notifier_chain_register(&test_chain, nb); if(err) goto out; out: return err; } EXPORT_SYMBOL(register_test_notifier); static int __init test_chain_0_init(void) { printk(KERN_DEBUG "I'm in test_chain_0\n"); return 0; } static void __exit test_chain_0_exit(void) { printk(KERN_DEBUG "Goodbye to test_chain_0\n"); // call_test_notifiers(TESTCHAIN_EXIT, (int *)NULL); } MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("fishOnFly"); module_init(test_chain_0_init); module_exit(test_chain_0_exit); /* test_chain_1.c :1. 定義回調函數;2. 定義notifier_block;3. 向chain_0注冊notifier_block;*/ #include <linux/notifier.h> #include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> /* printk() */ #include <linux/fs.h> /* everything() */ extern int register_test_notifier(struct notifier_block *nb); #define TESTCHAIN_INIT 0x52U /* realize the notifier_call func */ int test_init_event(struct notifier_block *nb, unsigned long event, void *v) { switch(event){ case TESTCHAIN_INIT: printk(KERN_DEBUG "I got the chain event: test_chain_2 is on the way of init\n"); break; default: break; } return NOTIFY_DONE; } /* define a notifier_block */ static struct notifier_block test_init_notifier = { .notifier_call = test_init_event, }; static int __init test_chain_1_init(void) { printk(KERN_DEBUG "I'm in test_chain_1\n"); register_test_notifier(&test_init_notifier);<span style="white-space:pre"> </span>// 由chain_0提供的設施 return 0; } static void __exit test_chain_1_exit(void) { printk(KERN_DEBUG "Goodbye to test_clain_l\n"); } MODULE_LICENSE("GPL"); MODULE_AUTHOR("fishOnFly"); module_init(test_chain_1_init); module_exit(test_chain_1_exit); /* test_chain_2.c:發出通知鏈事件*/ #include <linux/notifier.h> #include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> /* printk() */ #include <linux/fs.h> /* everything() */ extern int call_test_notifiers(unsigned long val, void *v); #define TESTCHAIN_INIT 0x52U static int __init test_chain_2_init(void) { printk(KERN_DEBUG "I'm in test_chain_2\n"); call_test_notifiers(TESTCHAIN_INIT, "no_use"); return 0; } static void __exit test_chain_2_exit(void) { printk(KERN_DEBUG "Goodbye to test_chain_2\n"); } MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("fishOnFly"); module_init(test_chain_2_init); module_exit(test_chain_2_exit); # Makefile # Comment/uncomment the following line to disable/enable debugging # DEBUG = y # Add your debugging flag (or not) to CFLAGS ifeq ($(DEBUG),y) DEBFLAGS = -O -g -DSCULL_DEBUG # "-O" is needed to expand inlines else DEBFLAGS = -O2 endif ifneq ($(KERNELRELEASE),) # call from kernel build system obj-m := test_chain_0.o test_chain_1.o test_chain_2.o else KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) modules: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules endif clean: rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions depend .depend dep: $(CC) $(CFLAGS) -M *.c > .depend ifeq (.depend,$(wildcard .depend)) include .depend endif [wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_0.ko [wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_1.ko [wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_2.ko [wang2@iwooing: notifier_chian]$ dmesg [ 5950.112649] I'm in test_chain_0 [ 5956.766610] I'm in test_chain_1 [ 5962.570003] I'm in test_chain_2 [ 5962.570008] I got the chain event: test_chain_2 is on the way of init [ 6464.042975] Goodbye to test_chain_2 [ 6466.368030] Goodbye to test_clain_l [ 6468.371479] Goodbye to test_chain_0
一、概述
內核許多子系統之間關聯緊密,因此在一個子系統發生或者檢測到的事件信息很可能對其他子系統來說也是有價值的。為了滿足其他子系統對這些事件信息的需求,即在某個子系統內發生或檢測到事件時,其他對此感興趣的子系統也能知道事件的發生,內核提供了notification chain機制。
注意:notification chain適用於內核子系統之間的信息傳遞,不涉及用戶態。
二、結構體
1. notifier_block
//action參數表示發生的事件類型,因為一個chain可能支持多個事件,此參數用來對事件進行區分
//void *data用來存放私有信息,其具體信息取決於特定的事件
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; //用於同一個chain中的notifier_block的鏈接 int priority;//表示notifier_call函數的優先級,在事件發生時先調用高優先級的回調函數。 };
notifier_call返回值為int類型,可能的返回值(include/linux/notifier.h文件中定義了這些常值)
//對該事件不感興趣 #define NOTIFY_DONE 0x0000 //成功響應該事件 #define NOTIFY_OK 0x0001 //該回調函數返回后停止處理后續notifier block #define NOTIFY_STOP_MASK 0x8000 //出錯,回調函數返回后停止處理后續notifier block #define NOTIFY_BAD (NOTIFY_STOP_MASK|0x0002) //成功響應事件,回調函數返回后停止處理后續notifier block #define NOTIFY_STOP (NOTIFY_OK|NOTIFY_STOP_MASK)
2. 圍繞核心數據結構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 notifierchains ):對通知鏈元素的回調函數沒有任何限制,所有鎖和保護機制都由調用者維護。對應的鏈表頭:
struct raw_notifier_head { struct notifier_block __rcu *head; };
4)SRCU 通知鏈( SRCU notifier chains ):可阻塞通知鏈的一種變體。對應的鏈表頭:
struct srcu_notifier_head { struct mutex mutex; struct srcu_struct srcu; struct notifier_block __rcu *head; };
三、操作過程
被通知一方(other_subsys_x)通過notifier_chain_register向特定的chain注冊回調函數,並且一般而言特定的子系統會用特定的notifier_chain_register包裝函數來注冊,比如路由子系統使用的是網絡子系統的:register_netdevice_notifier來注冊他的notifier_block。
1.向事件通知鏈注冊的步驟
(1)申明struct notifier_block結構
(2)編寫notifier_call函數
(3)調用特定的事件通知鏈的注冊函數,通過notifier_chain_register()將notifier_block注冊到通知鏈中。實際上notification chain就是一組函數列表。通常notification chain的名字的格式為xxx_chain、xxx_notifier_chain、 xxx_notifier_list,例如reboot_notifier_list。
static int notifier_chain_register(struct notifier_block **nl,struct notifier_block *n) { while ((*nl) != NULL) { //判斷優先權值, 優先權值越大位置越靠前 if (n->priority > (*nl)->priority) break; nl = &((*nl)->next); } n->next = *nl;//將節點n鏈接到鏈表nl中的合適位置 rcu_assign_pointer(*nl, n); return 0; }
2.通知子系統有事件發生
如果內核組件需要處理夠某個事件通知鏈上發出的事件通知,其就該在初始化時在該通知鏈上注冊回調函數。
inet_subsys是通過notifier_call_chain來通知其他的子系統的。notifier_call_chain會按照通知鏈上各成員的優先級順序執行回調函數(notifier_call_x);回調函數的執行現場在notifier_call_chain進程地址空間.
static int __kprobes notifier_call_chain(struct notifier_block **nl,unsigned long val, void *v, int nr_to_call, int *nr_calls) { int ret = NOTIFY_DONE; struct notifier_block *nb, *next_nb; //取通知鏈中的notifier_block nb = rcu_dereference_raw(*nl); while (nb && nr_to_call) { next_nb = rcu_dereference_raw(nb->next); //調用回調函數 ret = nb->notifier_call(nb, val, v); if (nr_calls) (*nr_calls)++; if ((ret & NOTIFY_STOP_MASK) == NOTIFY_STOP_MASK) break; nb = next_nb; nr_to_call--; } return ret; }
該函數將一個通知塊結構從通知鏈表中拆除:
static int notifier_chain_unregister(struct notifier_block **nl,struct notifier_block *n)
四、實例
1. 定義通知鏈並初始化
static struct srcu_notifier_head cpufreq_transition_notifier_list; srcu_init_notifier_head(&cpufreq_transition_notifier_list); void srcu_init_notifier_head(struct srcu_notifier_head *nh) { mutex_init(&nh->mutex); if (init_srcu_struct(&nh->srcu) < 0) BUG(); nh->head = NULL; }
2. 定義結構體和回調函數
static struct notifier_block __clk_cpufreq_notifier_block = { .notifier_call = __clk_cpufreq_notifier }; static int __clk_cpufreq_notifier(struct notifier_block *nb, unsigned long val, void *data) { printk("dump cpu freq\n",); return 0; }
2. 注冊
調用封裝函數cpufreq_register_notifier(&__clk_cpufreq_notifier_block,CPUFREQ_TRANSITION_NOTIFIER)注冊
int cpufreq_register_notifier(struct notifier_block *nb, unsigned int list) { int ret; WARN_ON(!init_cpufreq_transition_notifier_list_called); switch (list) { case CPUFREQ_TRANSITION_NOTIFIER: //將我們定義的notifier_block結構注冊到cpufreq_transition_notifier_list鏈中 ret = srcu_notifier_chain_register(&cpufreq_transition_notifier_list, nb); break; case CPUFREQ_POLICY_NOTIFIER: ret = blocking_notifier_chain_register( &cpufreq_policy_notifier_list, nb); break; default: ret = -EINVAL; } return ret; }
3.通知事件發生
void cpufreq_notify_transition(struct cpufreq_freqs *freqs, unsigned int state) { struct cpufreq_policy *policy; BUG_ON(irqs_disabled()); freqs->flags = cpufreq_driver->flags; pr_debug("notification %u of frequency transition to %u kHz\n",state, freqs->new); policy = per_cpu(cpufreq_cpu_data, freqs->cpu); switch (state) { case CPUFREQ_PRECHANGE: //...... //向通知鏈進行事件通知,最終會調用notifier_call_chain()函數,最終執行回調函數 srcu_notifier_call_chain(&cpufreq_transition_notifier_list,CPUFREQ_PRECHANGE, freqs); adjust_jiffies(CPUFREQ_PRECHANGE, freqs); break; //....... } }