1、通知鏈簡介
文本描述構成通知鏈的具體數據結構和API接口,同時描述四種通知鏈的具體應用場景,并對API接口進行簡要分析。
在Linux內核中,struct notifier_block 是一種數據結構,用于實現觀察者模式。它允許內核的不同部分將自己注冊為監聽器(觀察者)以偵聽特定事件。當這些事件發生時,內核會通知所有注冊的notifier block,它們可以對事件做出適當的響應。
struct notifier_block 在Linux內核頭文件 include/linux/notifier.h 中定義,并具有以下結構:
structnotifier_block{ int(*notifier_call)(structnotifier_block*nb,unsignedlongaction,void*data); structnotifier_block*next; intpriority; };
notifier_call:這個字段指向在通知事件發生時將被調用的回調函數。回調函數的函數簽名定義為 int (*notifier_call)(struct notifier_block *nb, unsigned long action, void *data)。nb 參數是指向 notifier block 本身的指針,action 包含通知類型,而 data 則是指向與事件相關的附加數據的指針。
next:這個字段是指向鏈中下一個 notifier block 的指針。Linux內核維護一個已注冊的 notifier block 的鏈表,該字段使得可以遍歷整個鏈表。
priority:這個字段決定了該 notifier block 相對于其他已注冊 notifier block 的優先級。當多個塊為同一事件注冊時,內核按照優先級降序通知它們。具有較高優先級值的 notifier block 將在具有較低優先級值的之前收到通知。
要使用 struct notifier_block,內核模塊可以使用Linux內核提供的函數進行注冊,例如register_inotifier() 或 register_netdevice_notifier(),具體取決于特定的事件類別。
一些常見的利用 struct notifier_block 的事件包括:
文件系統事件,如文件創建、刪除和修改。
網絡設備事件,如接口的啟用或禁用。
內存管理事件,如頁面分配和釋放。
通過使用 struct notifier_block,內核開發人員可以更好地設計模塊化和可擴展的系統,讓不同的組件以解耦的方式對事件做出響應。這種模式有助于更好地組織代碼,并且在不影響現有代碼的情況下更容易添加新功能到內核中。
整個結構如下圖所示:
2、通知鏈的類型
在linux內核中,定義了四種類型的通知鏈。
(1)原子(Atomic)通知鏈
定義如下:
原子通知鏈在內核中廣泛應用,特別是在一些基本的通知機制中。這種通知鏈的處理是原子的,意味著在處理鏈上的通知時,不會被中斷或其他并發操作干擾。原子通知鏈的應用場景包括進程退出通知、進程停止通知、以及內核調試和跟蹤事件通知等。
(2)阻塞(Block)通知鏈
定義如下:
阻塞通知鏈用于一些需要等待通知鏈中所有處理器完成后才能繼續執行的場景。當某個處理器在鏈上發起通知后,阻塞通知鏈將等待所有處理器都完成其任務后才返回。阻塞通知鏈的應用場景包括內核模塊的初始化,其中一個模塊可能需要等待其他模塊完成初始化后才能繼續執行。
(3)原始(RAW)通知鏈
定義如下:
原始通知鏈是一種特殊類型的通知鏈,它沒有任何同步機制。這意味著在處理通知鏈時,不進行任何鎖定或同步操作,這可能會導致并發問題。原始通知鏈主要用于一些低層的底層通知機制,通常需要使用者自己確保線程安全性。原始通知鏈的應用場景相對較少,可能只在一些特定的高性能場景中使用。
(4)SRCU通知鏈
定義如下:
SRCU通知鏈是通過Linux內核中的SRCU(Synchronize RCUs)機制來實現的。SRCU通知鏈提供了更高級的同步機制,以確保在刪除或釋放通知處理器時,不會出現競態條件。這允許在通知鏈上安全地添加和刪除處理器。SRCU通知鏈的應用場景包括網絡設備事件通知,其中多個處理器可能對事件做出響應,并且需要在處理器安全刪除時保持同步。
3、原理分析和API
(1)注銷通知器
在使用通知鏈之前,需要創建對應類型的通知鏈,并使用注冊進行注冊,從源碼角度,每種類型的通知鏈都一一對應著一個注冊函數:
原子通知鏈注冊函數:int atomic_notifier_chain_register(struct atomic_notifier_head *nh,struct notifier_block *nb)。
阻塞通知鏈注冊函數:int atomic_notifier_chain_register(struct blocking_notifier_head *nh,struct notifier_block *nb)。
原始通知鏈注冊函數:int atomic_notifier_chain_register(struct raw_notifier_head *nh,struct notifier_block *nb)。
srcu通知鏈注冊函數:int atomic_notifier_chain_register(struct srcu_notifier_head *nh,struct notifier_block *nb)。
上述四種類型的注冊函數本質上是調用notifier_chain_register()函數實現核心功能,該函數實現如下:
staticintnotifier_chain_register(structnotifier_block**nl, structnotifier_block*n) { while((*nl)!=NULL){ if(n->priority>(*nl)->priority) break; nl=&((*nl)->next); } n->next=*nl; rcu_assign_pointer(*nl,n); return0; }
上述代碼是一個根據優先級進行循環遍歷的操作,如果n的優先級比*nl的優先級高那么循環結束,接著就將n插入到*nl的前面。形成通知鏈。
(2)注銷通知器
有注冊函數,則對應著注銷函數,四種通知鏈的注銷函數是:
原子通知鏈注銷函數:int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh,struct notifier_block *nb);
阻塞通知鏈注銷函數:int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh,struct notifier_block *nb);
原始通知鏈注銷函數:int raw_notifier_chain_unregister(struct raw_notifier_head *nh,struct notifier_block *nb);
srcu通知鏈注銷函數:int srcu_notifier_chain_unregister(struct srcu_notifier_head *nh,struct notifier_block *nb);
上述四種類型的注冊函數本質上是調用notifier_chain_unregister()函數實現核心功能,該函數實現如下:
staticintnotifier_chain_unregister(structnotifier_block**nl, structnotifier_block*n) { while((*nl)!=NULL){ if((*nl)==n){ rcu_assign_pointer(*nl,n->next); return0; } nl=&((*nl)->next); } return-ENOENT; }
循環判斷找到了要注銷的然后執行注銷,將其從鏈表中移除。
(3)通知鏈的通知
通常,通知鏈的注冊是由各個模塊在內核初始化階段進行的。當特定事件發生時,內核會調用相應的notifier_call_chain()函數,以通知所有注冊的模塊或組件。這樣,不同的模塊可以根據事件類型和參數進行自定義處理,而無需顯式地知道其他模塊的存在。
四種通知鏈分別對應不同的函數:
原子通知鏈通知函數:int atomic_notifier_call_chain(struct atomic_notifier_head *nh,unsigned long val, void *v);
阻塞通知鏈通知函數:int blocking_notifier_call_chain(struct blocking_notifier_head *nh,unsigned long val, void *v);
原始通知鏈通知函數:int raw_notifier_call_chain(struct raw_notifier_head *nh, unsigned long val, void *v);
srcu通知鏈通知函數:int srcu_notifier_call_chain(struct srcu_notifier_head *nh, unsigned long val, void *v);
上述四個函數最后都會調用notifier_call_chain()實現核心功能,該函數實現如下:
staticintnotifier_call_chain(structnotifier_block**nl, unsignedlongval,void*v, intnr_to_call,int*nr_calls) { intret=NOTIFY_DONE; structnotifier_block*nb,*next_nb; nb=rcu_dereference_raw(*nl); while(nb&&nr_to_call){ next_nb=rcu_dereference_raw(nb->next); #ifdefCONFIG_DEBUG_NOTIFIERS if(unlikely(!func_ptr_is_kernel_text(nb->notifier_call))){ WARN(1,"Invalidnotifiercalled!"); 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--; } returnret; }
nl:指向通知鏈頭的指針。這是一個指向指針的指針,指向通知鏈的頭節點。
val:事件類型。鏈本身標識的一組事件,val明確標識一種事件類型
v:一個指針,指向攜帶更多事件相關信息的數據結構。
nr_to_call:記錄發送的通知數量。如果不需要這個字段的值可以是NULL
nr_calls:通知程序調用鏈返回最后一個被調用的通知程序函數返回的值。
在notifier_chain_unregister()的while循環結構中會調用:
ret=nb->notifier_call(nb,val,v);
依次執行注冊到該通知鏈中的所有函數。
4、實例代碼
本小節通過原子通知鏈給出實例代碼,原子通知鏈可用于實現觀察者模式的通信機制。
(1)定義一個通知鏈
#include#include #include #include /*printk()*/ //定義原子通知鏈 staticATOMIC_NOTIFIER_HEAD(my_notifier_list); //通知事件 staticintcall_notifiers(unsignedlongval,void*v) { returnatomic_notifier_call_chain(&my_notifier_list,val,v); } EXPORT_SYMBOL(call_notifiers); //向通知鏈注冊通知block staticintregister_notifier(structnotifier_block*nb) { interr; err=atomic_notifier_chain_register(&my_notifier_list,nb); if(err) returnerr; } EXPORT_SYMBOL(register_notifier); //從通知鏈中注銷通知block staticintunregister_notifier(structnotifier_block*nb) { interr; err=atomic_notifier_chain_unregister(&my_notifier_list,nb); if(err) returnerr; } EXPORT_SYMBOL(unregister_notifier); staticint__initmyNotifier_init(void) { printk("myNotifierinitfinish "); return0; } staticvoid__exitmyNotifier_exit(void) { printk("myNotifierexitfinish "); } module_init(myNotifier_init); module_exit(myNotifier_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("iriczhao");
(2)實現觀察者模塊
/** *模塊1,用于創建通知block,并注冊 */ #include#include #include externintregister_notifier(structnotifier_block*nb); externintunregister_notifier(structnotifier_block*nb); staticintnotifier_one_call_fn(structnotifier_block*nb, unsignedlongaction,void*data) { printk(">>thisisnotifier_one_call_fn "); printk("recvaction=%ddata=%p ",action,data); return0; } staticintnotifier_two_call_fn(structnotifier_block*nb, unsignedlongaction,void*data) { printk(">>thisisnotifier_two_call_fn "); return0; } /*defineanotifier_block*/ staticstructnotifier_blocknotifier_one={ .notifier_call=notifier_one_call_fn, }; staticstructnotifier_blocknotifier_two={ .notifier_call=notifier_two_call_fn, }; staticint__initmodule_1_init(void) { register_notifier(¬ifier_two); register_notifier(¬ifier_one); return0; } staticvoid__exitmodule_1_exit(void) { unregister_notifier(¬ifier_two); unregister_notifier(¬ifier_one); } module_init(module_1_init); module_exit(module_1_exit); //定義模塊相關信息 MODULE_AUTHOR("iriczhao"); MODULE_LICENSE("GPL");
(3)事件發生模塊
/* *事件通知模塊 */ #include#include #include #include externintcall_notifiers(unsignedlongval,void*v); staticintevent_module_init(void) { printk("Eventmoduleinitialized "); unsignedlongevent=123; void*data=(void*)0xDEADBEEF; call_notifiers(event,data); return0; } staticvoidevent_module_exit(void) { printk("Eventmoduleexiting "); } module_init(event_module_init); module_exit(event_module_exit); //定義模塊相關信息 MODULE_AUTHOR("iriczhao"); MODULE_LICENSE("GPL");
(4)輸出結果
將上述三份代碼以模塊方式構建,并加載進內核,首先加載自定義的通知鏈my_notifier_list,接著加載module_1.ko注冊兩個事件訂閱者,最后加載module_2.ko通知事件,并向module_1發送兩個參數:action和data,并通過module_1打印出來。輸出結果如下:
5、總結
本文描述了內核的通知鏈機制并對其進行了簡單的實踐,加深了對內核通知鏈的理解,方便對內核中基于通知鏈設計的代碼的執行行為的把控。
審核編輯:劉清
-
處理器
+關注
關注
68文章
19896瀏覽量
235354 -
LINUX內核
+關注
關注
1文章
317瀏覽量
22412 -
API接口
+關注
關注
1文章
85瀏覽量
10889 -
RAW
+關注
關注
0文章
21瀏覽量
4033
原文標題:接著整!玩一玩linux內核的通知鏈
文章出處:【微信號:嵌入式小生,微信公眾號:嵌入式小生】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
嵌入式Linux之異步通知方式問題匯總
玩一玩旋轉編碼器模塊
榮耀暢玩7X和暢玩6X哪個好?對比測評
Linux內核通知鏈如何引入?原理是什么?如何使用和實現?及實例分析

榮耀暢玩7X怎么樣
需要了解Linux內核通知鏈機制的原理及實現

評論