生活随笔
收集整理的這篇文章主要介紹了
Linux内核通知链机制的原理及实现【转】
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
轉自:http://www.cnblogs.com/armlinux/archive/2011/11/11/2396781.html
一、概念:
??? 大多數內核子系統都是相互獨立的,因此某個子系統可能對其它子系統產生的事件感興趣。為了滿足這個需求,也即是讓某個子系統在發生某個事件時通知其它的子系統,Linux內核提供了通知鏈的機制。通知鏈表只能夠在內核的子系統之間使用,而不能夠在內核與用戶空間之間進行事件的通知。 通知鏈表是一個函數鏈表,鏈表上的每一個節點都注冊了一個函數。當某個事情發生時,鏈表上所有節點對應的函數就會被執行。所以對于通知鏈表來說有一個通知方與一個接收方。在通知這個事件時所運行的函數由被通知方決定,實際上也即是被通知方注冊了某個函數,在發生某個事件時這些函數就得到執行。其實和系統調用signal的思想差不多。
二、數據結構:
通知鏈有四種類型:
原子通知鏈(?Atomic?notifier?chains?):通知鏈元素的回調函數(當事件發生時要執行的函數)只能在中斷上下文中運行,不允許阻塞。對應的鏈表頭結構: struct?atomic_notifier_head? { ????spinlock_t lock; ????struct?notifier_block?*head; };
可阻塞通知鏈(?Blocking?notifier?chains?):通知鏈元素的回調函數在進程上下文中運行,允許阻塞。對應的鏈表頭: struct?blocking_notifier_head? { ????struct?rw_semaphore rwsem; ????struct?notifier_block?*head; };
原始通知鏈(?Raw?notifier?chains?):對通知鏈元素的回調函數沒有任何限制,所有鎖和保護機制都由調用者維護。對應的鏈表頭: struct?raw_notifier_head? { ????struct?notifier_block?*head; };
SRCU?通知鏈(?SRCU?notifier?chains?):可阻塞通知鏈的一種變體。對應的鏈表頭: struct?srcu_notifier_head? { ????struct?mutex mutex; ????struct?srcu_struct srcu; ????struct?notifier_block?*head; };
通知鏈的核心結構:
struct?notifier_block? { ????int?(*notifier_call)(struct?notifier_block?*,?unsigned?long,?void?*); ????struct?notifier_block?*next; ????int?priority; };
?
其中notifier_call是通知鏈要執行的函數指針,next用來連接其它的通知結構,priority是這個通知的優先級,同一條鏈上的notifier_block{}是按優先級排列的。內核代碼中一般把通知鏈命名為xxx_chain,?xxx_nofitier_chain這種形式的變量名。
三、運作機制:
通知鏈的運作機制包括兩個角色:
被通知者:對某一事件感興趣一方。定義了當事件發生時,相應的處理函數,即回調函數。但需要事先將其注冊到通知鏈中(被通知者注冊的動作就是在通知鏈中增加一項)。 通知者:事件的通知者。當檢測到某事件,或者本身產生事件時,通知所有對該事件感興趣的一方事件發生。他定義了一個通知鏈,其中保存了每一個被通知者對事件的處理函數(回調函數)。通知這個過程實際上就是遍歷通知鏈中的每一項,然后調用相應的事件處理函數。 包括以下過程:
通知者定義通知鏈。 被通知者向通知鏈中注冊回調函數。 當事件發生時,通知者發出通知(執行通知鏈中所有元素的回調函數)。 被通知者調用?notifier_chain_register?函數注冊回調函數,該函數按照優先級將回調函數加入到通知鏈中:
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; ????rcu_assign_pointer(*nl,?n); ???? ????return?0; }
?
注銷回調函數則使用?notifier_chain_unregister?函數,即將回調函數從通知鏈中刪除:
static?int?notifier_chain_unregister(struct?notifier_block?**nl,?struct?notifier_block?*n) { ????while?((*nl)?!=?NULL)? ????{ ????????if?((*nl)?==?n)? ????????{ ????????????rcu_assign_pointer(*nl,?n->next); ???????? ????????????return?0; ????????} ???? ????????nl?=?&((*nl)->next); ????} ???? ????return?-ENOENT; }
?
通知者調用?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; ???? ????nb?=?rcu_dereference(*nl); ???? ????while?(nb?&&?nr_to_call)? ????{ ????????next_nb?=?rcu_dereference(nb->next); ???? #ifdef?CONFIG_DEBUG_NOTIFIERS ????????if?(unlikely(!func_ptr_is_kernel_text(nb->notifier_call)))? ????????{ ????????????WARN(1,?"Invalid notifier called!"); ???????????? ????????????nb?=?next_nb; ???????????? ????????????continue; ????????} #endif ????????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; }
?
??? 參數nl是通知鏈的頭部,val表示事件類型,v用來指向通知鏈上的函數執行時需要用到的參數,一般不同的通知鏈,參數類型也不一樣,例如當通知一個網卡被注冊時,v就指向net_device結構,nr_to_call表示準備最多通知幾個,-1表示整條鏈都通知,nr_calls非空的話,返回通知了多少個。
????每個被執行的notifier_block回調函數的返回值可能取值為以下幾個:
NOTIFY_DONE:表示對相關的事件類型不關心。 NOTIFY_OK:順利執行。 NOTIFY_BAD:執行有錯。 NOTIFY_STOP:停止執行后面的回調函數。 NOTIFY_STOP_MASK:停止執行的掩碼。 ??? Notifier_call_chain()把最后一個被調用的回調函數的返回值作為它的返回值。
四、舉例應用:
在這里,寫了一個簡單的通知鏈表的代碼。實際上,整個通知鏈的編寫也就兩個過程:
首先是定義自己的通知鏈的頭節點,并將要執行的函數注冊到自己的通知鏈中。 其次則是由另外的子系統來通知這個鏈,讓其上面注冊的函數運行。 ????? 這里將第一個過程分成了兩步來寫,第一步是定義了頭節點和一些自定義的注冊函數(針對該頭節點的),第二步則是使用自定義的注冊函數注冊了一些通知鏈節點。分別在代碼buildchain.c與regchain.c中。發送通知信息的代碼為notify.c。
代碼1?buildchain.c。它的作用是自定義一個通知鏈表test_chain,然后再自定義兩個函數分別向這個通知鏈中加入或刪除節點,最后再定義一個函數通知這個test_chain鏈:
#include?<asm/uaccess.h> #include?<linux/types.h> #include?<linux/kernel.h> #include?<linux/sched.h> #include?<linux/notifier.h> #include?<linux/init.h> #include?<linux/types.h> #include?<linux/module.h> MODULE_LICENSE("GPL"); /* * 定義自己的通知鏈頭結點以及注冊和卸載通知鏈的外包函數 */ /* * RAW_NOTIFIER_HEAD是定義一個通知鏈的頭部結點, * 通過這個頭部結點可以找到這個鏈中的其它所有的notifier_block */ static?RAW_NOTIFIER_HEAD(test_chain); /* * 自定義的注冊函數,將notifier_block節點加到剛剛定義的test_chain這個鏈表中來 * raw_notifier_chain_register會調用notifier_chain_register */ int?register_test_notifier(struct?notifier_block?*nb) { ??return?raw_notifier_chain_register(&test_chain,?nb); } EXPORT_SYMBOL(register_test_notifier); int?unregister_test_notifier(struct?notifier_block?*nb) { ??return?raw_notifier_chain_unregister(&test_chain,?nb); } EXPORT_SYMBOL(unregister_test_notifier); /* * 自定義的通知鏈表的函數,即通知test_chain指向的鏈表中的所有節點執行相應的函數 */ int?test_notifier_call_chain(unsigned?long?val,?void?*v) { ??return?raw_notifier_call_chain(&test_chain,?val,?v); } EXPORT_SYMBOL(test_notifier_call_chain); /* * init and exit? */ static?int?__init init_notifier(void) { ??printk("init_notifier\n"); ??return?0; } static?void?__exit exit_notifier(void) { ????printk("exit_notifier\n"); } module_init(init_notifier); module_exit(exit_notifier);
?
代碼2?regchain.c。該代碼的作用是將test_notifier1?test_notifier2?test_notifier3這三個節點加到之前定義的test_chain這個通知鏈表上,同時每個節點都注冊了一個函數:
#include?<asm/uaccess.h> #include?<linux/types.h> #include?<linux/kernel.h> #include?<linux/sched.h> #include?<linux/notifier.h> #include?<linux/init.h> #include?<linux/types.h> #include?<linux/module.h> MODULE_LICENSE("GPL"); /* * 注冊通知鏈 */ extern?int?register_test_notifier(struct?notifier_block*); extern?int?unregister_test_notifier(struct?notifier_block*); static?int?test_event1(struct?notifier_block?*this,?unsigned?long?event,?void?*ptr) { ??printk("In Event 1: Event Number is %d\n",?event); ??return?0;? } static?int?test_event2(struct?notifier_block?*this,?unsigned?long?event,?void?*ptr) { ??printk("In Event 2: Event Number is %d\n",?event); ??return?0;? } static?int?test_event3(struct?notifier_block?*this,?unsigned?long?event,?void?*ptr) { ??printk("In Event 3: Event Number is %d\n",?event); ??return?0;? } /* * 事件1,該節點執行的函數為test_event1 */ static?struct?notifier_block test_notifier1?= { ????.notifier_call?=?test_event1, }; /* * 事件2,該節點執行的函數為test_event1 */ static?struct?notifier_block test_notifier2?= { ????.notifier_call?=?test_event2, }; /* * 事件3,該節點執行的函數為test_event1 */ static?struct?notifier_block test_notifier3?= { ????.notifier_call?=?test_event3, }; /* * 對這些事件進行注冊 */ static?int?__init reg_notifier(void) { ??int?err; ??printk("Begin to register:\n"); ?? ??err?=?register_test_notifier(&test_notifier1); ??if?(err) ??{ ????printk("register test_notifier1 error\n"); ????return?-1;? ??} ??printk("register test_notifier1 completed\n"); ??err?=?register_test_notifier(&test_notifier2); ??if?(err) ??{ ????printk("register test_notifier2 error\n"); ????return?-1;? ??} ??printk("register test_notifier2 completed\n"); ??err?=?register_test_notifier(&test_notifier3); ??if?(err) ??{ ????printk("register test_notifier3 error\n"); ????return?-1;? ??} ??printk("register test_notifier3 completed\n"); ?? ??return?err; } /* * 卸載剛剛注冊了的通知鏈 */ static?void?__exit unreg_notifier(void) { ??printk("Begin to unregister\n"); ??unregister_test_notifier(&test_notifier1); ??unregister_test_notifier(&test_notifier2); ??unregister_test_notifier(&test_notifier3); ??printk("Unregister finished\n"); } module_init(reg_notifier); module_exit(unreg_notifier);
?
代碼3?notify.c。該代碼的作用就是向test_chain通知鏈中發送消息,讓鏈中的函數運行:
#include?<asm/uaccess.h> #include?<linux/types.h> #include?<linux/kernel.h> #include?<linux/sched.h> #include?<linux/notifier.h> #include?<linux/init.h> #include?<linux/types.h> #include?<linux/module.h> MODULE_LICENSE("GPL"); extern?int?test_notifier_call_chain(unsigned?long?val,?void?*v); /* * 向通知鏈發送消息以觸發注冊了的函數 */ static?int?__init call_notifier(void) { ??int?err; ??printk("Begin to notify:\n"); ??/* ??* 調用自定義的函數,向test_chain鏈發送消息 ??*/ ??printk("==============================\n"); ??err?=?test_notifier_call_chain(1,?NULL); ??printk("==============================\n"); ??if?(err) ??????????printk("notifier_call_chain error\n"); ??return?err; } static?void?__exit uncall_notifier(void) { ????printk("End notify\n"); } module_init(call_notifier); module_exit(uncall_notifier);
?
Makefile文件:
obj-m:=buildchain.o regchain.o notify.o CURRENT_PATH?:=?$(shell pwd) LINUX_KERNEL?:=?$(shell uname?-r) KERNELDIR?:=?/usr/src/linux-headers-$(LINUX_KERNEL) all: make?-C $(KERNELDIR)?M=$(CURRENT_PATH)?modules clean: make?-C $(KERNELDIR)?M=$(CURRENT_PATH)?clean?
?
運行(注意insmod要root權限):
make insmod buildchain.ko insmod regchain.ko insmod notify.ko
?
這樣就可以看到通知鏈運行的效果了:
init_notifier Begin to?register: register?test_notifier1 completed register?test_notifier2 completed register?test_notifier3 completed Begin to notify: ============================== In Event 1:?Event Number is 1 In Event 2:?Event Number is 1 In Event 3:?Event Number is 1 ==============================
分類:?Linux 【作者】張昺華 【出處】http://www.cnblogs.com/sky-heaven/ 【博客園】 http://www.cnblogs.com/sky-heaven/ 【新浪博客】 http://blog.sina.com.cn/u/2049150530 【知乎】 http://www.zhihu.com/people/zhang-bing-hua 【我的作品---旋轉倒立擺】 http://v.youku.com/v_show/id_XODM5NDAzNjQw.html?spm=a2hzp.8253869.0.0&from=y1.7-2 【我的作品---自平衡自動循跡車】 http://v.youku.com/v_show/id_XODM5MzYyNTIw.html?spm=a2hzp.8253869.0.0&from=y1.7-2 【新浪微博】 張昺華--sky 【twitter】 @sky2030_ 【facebook】 張昺華 zhangbinghua 本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利.
總結
以上是生活随笔 為你收集整理的Linux内核通知链机制的原理及实现【转】 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。