linux內核的通知鏈機制

一、爲什麼需要通知鏈:

   linux內核的各個子系統之間往往互相關聯,一個子系統產生或者偵測到的事件,其它的子系統往往也很感興趣,因此linux內核採用了通知鏈機制實現內核的子系統之間的通信需求。值的注意的是,通知鏈機制僅用於內核內部的子系統之間的通信,內核與用戶空間的通信依賴於其它機制,如系統調用、procfs、sysctl、ioctl等。

   以下圖爲例,路由器RT直接連接網絡A、B、C、E,間接連接網絡D、F(通過另一個地址爲IP1的路由器),其路由表如圖。如果eth3連接網絡E的通路斷掉了(管理員下達指令ifconfigeth3down,或者硬件錯誤等原因),那麼此時路由器RT就需要更新其路由表,如刪除D、E、F網絡相關的路由項目,那麼誰去告訴路由器RT的路由子系統,說eth3的鏈路已經斷開了呢,那就是通知鏈機制,也就是說此時會有內核的其它子系統發出通知消息,告訴路由子系統eth3接口已經斷開,路由子系統接收到這個通知消息後就會更新自己的路由表。

linux內核的通知鏈機制

 

    對於上面的情形,考慮一個沒有通知鏈機制的情景,內核的某個子系統產生或者檢測到了一個事件,該子系統知道,內核的其它子系統可能也對此很感興趣,於是它就會:

If (subsystem_X_enabled) {
do_something_1
}
if (subsystem_Y_enabled) {
do_something_2
}
If (subsystem_Z_enabled) {
do_something_3
}
... ... ...

    它覺得有幾個子系統對這個事件感興趣,那麼它就需要幾條相應的條件語句,首先,讓它知道哪些子系統對此感興趣就已經很困難了,而即使這點可以辦到,還有一個更嚴重的問題,假設某一天內核被添加了一個子系統進來,而這個子系統對其它子系統產生的事件都可能感興趣,那麼怎麼辦?必須修改其它所有子系統對其事件的處理,也就是說其它每個子系統對於每個事件的處理函數裏面都必須加上一條if語句,崩潰不?

    而如果採用通知鏈機制,也就是說某個子系統提供對事件進行處理的回調函數,其它子系統發來某個事件時,只要調用相應的回調函數就可以順利地處理該事件。那即使內核再加入其它的子系統,也只需要在增加的子系統裏面添加它感興趣的事件的回調函數就可以,而不需要修改其它的子系統了。

 

二、什麼是通知鏈機制     

   簡單地說,通知鏈其實就是一組函數,這些函數在發生了某個事件時被觸發執行,其結果就是通知其它的子系統發生了某個事件。對每個通知來說,都有一個主動方(通知者)和一個被動方(被通知者),也就是所謂的publish-and-subscribe模型:

    1.被通知方是通知的接收子系統,該子系統會提供一個回調函數

    2.通知方是通知的發送子系統,它會調用被動方提供的回調函數,具體執行哪個回調函數由被通知方決定,而不是產生事件的子系統。

   通知鏈機制實現了各個子系統之間的事件信息共享功能,每個子系統只需要關心其它子系統可能產生的事件哪些是自己感興趣的,或者自己產生的事件對其它哪些子系統有意義就可以,而不需要關心自己感興趣的事件是怎樣產生的,自己產生的事件被關注的子系統是具體的哪個,以及它爲什麼會對此感興趣等等。

 

三、怎樣定義一個鏈

通知鏈列表元素的類型是

struct notifier_block
{
int (*notifier_call)(struct notifier_block *self, unsigned long,void *);
struct notifier_block *next;
int priority;
};

notifier_call就是回調函數,priority是函數的優先級,但實際實現過程中,這個值通常不設置,即讓它取默認值0,因此回調函數的執行順序就決定於註冊的順序(半隨機順序)。定義某個notifier_block實例時通常使用的名字如xxx_chain,xxx_notifier_chain, xxx_notifier_list.

 

四、註冊一個鏈(將某個鏈元素插入list)

定義好一個鏈,還需要對它進行註冊。當某個子系統對定義的某個通知鏈感興趣時,它就會使用notifier_chain_register函數來註冊這個鏈。這個函數還有一些包裹函數,如對於inetaddr_chain,
inet6addr_chain , netdev_chain這三條鏈,對應的註冊,刪除和通知函數(及其包裹函數)如下:

   1.Registration
int notifier_chain_register(struct notifier_block **list, structnotifier_block *n)

將新的節點n插入list指向的鏈表中
   Wrappers
inetaddr_chain: register_inetaddr_notifier

inet6addr_chain: register_inet6addr_notifier

netdev_chain: register_netdevice_notifier


   2.Unregistration
int notifier_chain_unregister(struct notifier_block **nl, structnotifier_block *n)

從鏈表nl中刪除節點n
   Wrappers
inetaddr_chain: unregister_inetaddr_notifier

inet6addr_chain: unregister_inet6addr_notifier

netdev_chain: unregister_netdevice_notifier

 

   3.Notification
int notifier_call_chain(struct notifier_block **n, unsigned longval, void *v)

 

   通知鏈中的notifier_block元素按照優先級組織成一個鏈表,優先級相同的元素按照插入時間排序。對通知鏈的訪問受到notifier_lock鎖的保護,linux內核對所有的通知鏈只定義了一個鎖,這並不影響性能,因爲所有的notifier_call函數都是在系統引導或者模塊加載的時候就已經註冊了的,之後對通知鏈的訪問都是隻讀的。

   notifier_chain_register函數的實現如下,它會遍歷list,找到n的優先級對應的位置進行插入:

int notifier_chain_register(struct notifier_block **list, structnotifier_block *n)
{
   write_lock(&notifier_lock);

   while(*list)
    {
       if(n->priority >(*list)->priority)
       break;
       list= &((*list)->next);
    }
   n->next = *list;
   *list=n;
   write_unlock(&notifier_lock);
    return0;
}

 

五、向通知鏈發送通知消息

   通知消息通過notifier_call_chain函數(kernel/sys.c)產生,函數只是簡單地按優先級次序調用通知鏈中註冊的回調函數。值的注意的是,該函數是在調用它的進程的進程上下文中執行的,而回調函數也可以設計成把通知消息排進某處的隊列,然後喚醒查看此通知消息的進程。

int notifier_call_chain(struct notifier_block **n, unsigned longval, void *v)
{
int ret = NOTIFY_DONE;
struct notifier_block *nb = *n;
while (nb)
{
ret = nb->notifier_call(nb, val, v);
if (ret & NOTIFY_STOP_MASK)
{
return ret;
}
nb = nb->next;
}
return ret;
}

其中對通知鏈調用的回調函數(notifier_call)的返回值可以是如下數值,它們被定義於/include/linux/norifier.h中:

   NOTIFY_OK
Notification was processed correctly.
   NOTIFY_DONE
Not interested in the notification. 不要用來替代NOTIFY_OK.
   NOTIFY_BAD
Something went wrong. Stop calling the callback routines for thisevent.
   NOTIFY_STOP
Routine invoked correctly. However, no further callbacks need to becalled for this event.
    NOTIFY_STOP_MASK
This flag is checked by notifier_call_chain to see whether to stopinvoking the callback
routines, or keep going. Both NOTIFY_BAD and NOTIFY_STOP includethis flag in their
definitions.

注意,對於同一個通知鏈,在同一時間,有可能不同的CPU會同時調用notofier_call_chain,而負責互斥訪問和串行化的是回調函數

     

 六、網絡子系統相關的通知鏈

   內核定義了至少10種不同的通知鏈,網絡相關的通知鏈有:

inetaddr_chain:有關本地接口的ipv4地址的插入刪除變更等的通知信息,ipv6使用類型的鏈inet6addr_chain

netdev_chain:有關網絡設備註冊狀態的通知信息

 

通知鏈註冊通常發生在感興趣的內核組建初始化的時候,例如路由系統初始化時調用的函數ip_fib_init會註冊兩條相關的鏈:(code in /net/ipv4/fib_frontend.c)

static struct notifier_block fib_inetaddr_notifier = {
.notifier_call = fib_inetaddr_event,
};
static struct notifier_block fib_netdev_notifier = {
.notifier_call = fib_netdev_event,
};
void _ _init ip_fib_init(void)
{
... ... ...
register_netdevice_notifier(&fib_netdev_notifier);
register_inetaddr_notifier(&fib_inetaddr_notifier);
}

 

 

七、定義自己的通知鏈

1.定義通知鏈的頭節點

2.把某個事件發生時需要執行的回調函數以節點的形式插入鏈表

3.發生了感興趣的時間,notifier_call_chain執行,鏈上註冊的回調函數得到調用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章