天天看點

Linux核心通知鍊機制的原理及實作【轉】

一、概念:

    大多數核心子系統都是互相獨立的,是以某個子系統可能對其它子系統産生的事件感興趣。為了滿足這個需求,也即是讓某個子系統在發生某個事件時通知其它的子系統,Linux核心提供了通知鍊的機制。通知連結清單隻能夠在核心的子系統之間使用,而不能夠在核心與使用者空間之間進行事件的通知。 通知連結清單是一個函數連結清單,連結清單上的每一個節點都注冊了一個函數。當某個事情發生時,連結清單上所有節點對應的函數就會被執行。是以對于通知連結清單來說有一個通知方與一個接收方。在通知這個事件時所運作的函數由被通知方決定,實際上也即是被通知方注冊了某個函數,在發生某個事件時這些函數就得到執行。其實和系統調用signal的思想差不多。

二、資料結構:

通知鍊有四種類型:

原子通知鍊( Atomic notifier chains ):通知鍊元素的回調函數(當事件發生時要執行的函數)隻能在中斷上下文中運作,不允許阻塞。對應的連結清單頭結構:

<code>struct atomic_notifier_head  {     spinlock_t lock;     struct notifier_block *head; };</code>

可阻塞通知鍊( Blocking notifier chains ):通知鍊元素的回調函數在程序上下文中運作,允許阻塞。對應的連結清單頭:

<code>struct blocking_notifier_head  {     struct rw_semaphore rwsem;     struct notifier_block *head; };</code>

原始通知鍊( Raw notifier chains ):對通知鍊元素的回調函數沒有任何限制,所有鎖和保護機制都由調用者維護。對應的連結清單頭:

<code>struct raw_notifier_head  {     struct notifier_block *head; };</code>

SRCU 通知鍊( SRCU notifier chains ):可阻塞通知鍊的一種變體。對應的連結清單頭:

<code>struct srcu_notifier_head  {     struct mutex mutex;     struct srcu_struct srcu;     struct notifier_block *head; };</code>

通知鍊的核心結構:

<code>struct notifier_block  {     int (*notifier_call)(struct notifier_block *, unsigned long, void *);     struct notifier_block *next;     int priority; };</code>

其中notifier_call是通知鍊要執行的函數指針,next用來連接配接其它的通知結構,priority是這個通知的優先級,同一條鍊上的notifier_block{}是按優先級排列的。核心代碼中一般把通知鍊命名為xxx_chain, xxx_nofitier_chain這種形式的變量名。

三、運作機制:

通知鍊的運作機制包括兩個角色:

被通知者:對某一事件感興趣一方。定義了當事件發生時,相應的處理函數,即回調函數。但需要事先将其注冊到通知鍊中(被通知者注冊的動作就是在通知鍊中增加一項)。

通知者:事件的通知者。當檢測到某事件,或者本身産生事件時,通知所有對該事件感興趣的一方事件發生。他定義了一個通知鍊,其中儲存了每一個被通知者對事件的處理函數(回調函數)。通知這個過程實際上就是周遊通知鍊中的每一項,然後調用相應的事件處理函數。

包括以下過程:

通知者定義通知鍊。

被通知者向通知鍊中注冊回調函數。

當事件發生時,通知者發出通知(執行通知鍊中所有元素的回調函數)。

被通知者調用 notifier_chain_register 函數注冊回調函數,該函數按照優先級将回調函數加入到通知鍊中:

<code>static int notifier_chain_register(struct notifier_block **nl, struct notifier_block *n) {     while ((*nl) != NULL)      {         if (n-&gt;priority &gt; (*nl)-&gt;priority)         break;         nl = &amp;((*nl)-&gt;next);     }          n-&gt;next = *nl;     rcu_assign_pointer(*nl, n);          return 0; }</code>

登出回調函數則使用 notifier_chain_unregister 函數,即将回調函數從通知鍊中删除:

<code>static int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n) {     while ((*nl) != NULL)      {         if ((*nl) == n)          {             rcu_assign_pointer(*nl, n-&gt;next);                      return 0;         }              nl = &amp;((*nl)-&gt;next);     }          return -ENOENT; }</code>

通知者調用 notifier_call_chain 函數通知事件的到達,這個函數會周遊通知鍊中所有的元素,然後依次調用每一個的回調函數(即完成通知動作):

<code>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 &amp;&amp; nr_to_call)      {         next_nb = rcu_dereference(nb-&gt;next);      #ifdef CONFIG_DEBUG_NOTIFIERS         if (unlikely(!func_ptr_is_kernel_text(nb-&gt;notifier_call)))          {             WARN(1, "Invalid notifier called!");                          nb = next_nb;                          continue;         } #endif         ret = nb-&gt;notifier_call(nb, val, v);                  if (nr_calls)                  (*nr_calls)++;                  if ((ret &amp; NOTIFY_STOP_MASK) == NOTIFY_STOP_MASK)                  break;                  nb = next_nb;                  nr_to_call--;     }          return ret; }</code>

    參數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鍊:

<code>#include &lt;asm/uaccess.h&gt; #include &lt;linux/types.h&gt; #include &lt;linux/kernel.h&gt; #include &lt;linux/sched.h&gt; #include &lt;linux/notifier.h&gt; #include &lt;linux/init.h&gt; #include &lt;linux/types.h&gt; #include &lt;linux/module.h&gt; 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(&amp;test_chain, nb); } EXPORT_SYMBOL(register_test_notifier); int unregister_test_notifier(struct notifier_block *nb) {   return raw_notifier_chain_unregister(&amp;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(&amp;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);</code>

代碼2 regchain.c。該代碼的作用是将test_notifier1 test_notifier2 test_notifier3這三個節點加到之前定義的test_chain這個通知連結清單上,同時每個節點都注冊了一個函數:

<code>#include &lt;asm/uaccess.h&gt; #include &lt;linux/types.h&gt; #include &lt;linux/kernel.h&gt; #include &lt;linux/sched.h&gt; #include &lt;linux/notifier.h&gt; #include &lt;linux/init.h&gt; #include &lt;linux/types.h&gt; #include &lt;linux/module.h&gt; 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(&amp;test_notifier1);   if (err)   {     printk("register test_notifier1 error\n");     return -1;    }   printk("register test_notifier1 completed\n");   err = register_test_notifier(&amp;test_notifier2);   if (err)   {     printk("register test_notifier2 error\n");     return -1;    }   printk("register test_notifier2 completed\n");   err = register_test_notifier(&amp;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(&amp;test_notifier1);   unregister_test_notifier(&amp;test_notifier2);   unregister_test_notifier(&amp;test_notifier3);   printk("Unregister finished\n"); } module_init(reg_notifier); module_exit(unreg_notifier);</code>

代碼3 notify.c。該代碼的作用就是向test_chain通知鍊中發送消息,讓鍊中的函數運作:

<code>#include &lt;asm/uaccess.h&gt; #include &lt;linux/types.h&gt; #include &lt;linux/kernel.h&gt; #include &lt;linux/sched.h&gt; #include &lt;linux/notifier.h&gt; #include &lt;linux/init.h&gt; #include &lt;linux/types.h&gt; #include &lt;linux/module.h&gt; 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);</code>

Makefile檔案:

<code>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 </code>

運作(注意insmod要root權限):

<code>make insmod buildchain.ko insmod regchain.ko insmod notify.ko</code>

這樣就可以看到通知鍊運作的效果了:

<code>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 ==============================</code>

【新浪微網誌】 張昺華--sky

【twitter】 @sky2030_

【facebook】 張昺華 zhangbinghua

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利.

繼續閱讀