1、通知鏈簡介
文本描述構(gòu)成通知鏈的具體數(shù)據(jù)結(jié)構(gòu)和API接口,同時描述四種通知鏈的具體應(yīng)用場景,并對API接口進行簡要分析。
在Linux內(nèi)核中,struct notifier_block 是一種數(shù)據(jù)結(jié)構(gòu),用于實現(xiàn)觀察者模式。它允許內(nèi)核的不同部分將自己注冊為監(jiān)聽器(觀察者)以偵聽特定事件。當(dāng)這些事件發(fā)生時,內(nèi)核會通知所有注冊的notifier block,它們可以對事件做出適當(dāng)?shù)捻憫?yīng)。
struct notifier_block 在Linux內(nèi)核頭文件 include/linux/notifier.h 中定義,并具有以下結(jié)構(gòu):
structnotifier_block{ int(*notifier_call)(structnotifier_block*nb,unsignedlongaction,void*data); structnotifier_block*next; intpriority; };
notifier_call:這個字段指向在通知事件發(fā)生時將被調(diào)用的回調(diào)函數(shù)?;卣{(diào)函數(shù)的函數(shù)簽名定義為 int (*notifier_call)(struct notifier_block *nb, unsigned long action, void *data)。nb 參數(shù)是指向 notifier block 本身的指針,action 包含通知類型,而 data 則是指向與事件相關(guān)的附加數(shù)據(jù)的指針。
next:這個字段是指向鏈中下一個 notifier block 的指針。Linux內(nèi)核維護一個已注冊的 notifier block 的鏈表,該字段使得可以遍歷整個鏈表。
priority:這個字段決定了該 notifier block 相對于其他已注冊 notifier block 的優(yōu)先級。當(dāng)多個塊為同一事件注冊時,內(nèi)核按照優(yōu)先級降序通知它們。具有較高優(yōu)先級值的 notifier block 將在具有較低優(yōu)先級值的之前收到通知。
要使用 struct notifier_block,內(nèi)核模塊可以使用Linux內(nèi)核提供的函數(shù)進行注冊,例如register_inotifier() 或 register_netdevice_notifier(),具體取決于特定的事件類別。
一些常見的利用 struct notifier_block 的事件包括:
文件系統(tǒng)事件,如文件創(chuàng)建、刪除和修改。
網(wǎng)絡(luò)設(shè)備事件,如接口的啟用或禁用。
內(nèi)存管理事件,如頁面分配和釋放。
通過使用 struct notifier_block,內(nèi)核開發(fā)人員可以更好地設(shè)計模塊化和可擴展的系統(tǒng),讓不同的組件以解耦的方式對事件做出響應(yīng)。這種模式有助于更好地組織代碼,并且在不影響現(xiàn)有代碼的情況下更容易添加新功能到內(nèi)核中。
整個結(jié)構(gòu)如下圖所示:
2、通知鏈的類型
在linux內(nèi)核中,定義了四種類型的通知鏈。
(1)原子(Atomic)通知鏈
定義如下:
原子通知鏈在內(nèi)核中廣泛應(yīng)用,特別是在一些基本的通知機制中。這種通知鏈的處理是原子的,意味著在處理鏈上的通知時,不會被中斷或其他并發(fā)操作干擾。原子通知鏈的應(yīng)用場景包括進程退出通知、進程停止通知、以及內(nèi)核調(diào)試和跟蹤事件通知等。
(2)阻塞(Block)通知鏈
定義如下:
阻塞通知鏈用于一些需要等待通知鏈中所有處理器完成后才能繼續(xù)執(zhí)行的場景。當(dāng)某個處理器在鏈上發(fā)起通知后,阻塞通知鏈將等待所有處理器都完成其任務(wù)后才返回。阻塞通知鏈的應(yīng)用場景包括內(nèi)核模塊的初始化,其中一個模塊可能需要等待其他模塊完成初始化后才能繼續(xù)執(zhí)行。
(3)原始(RAW)通知鏈
定義如下:
原始通知鏈?zhǔn)且环N特殊類型的通知鏈,它沒有任何同步機制。這意味著在處理通知鏈時,不進行任何鎖定或同步操作,這可能會導(dǎo)致并發(fā)問題。原始通知鏈主要用于一些低層的底層通知機制,通常需要使用者自己確保線程安全性。原始通知鏈的應(yīng)用場景相對較少,可能只在一些特定的高性能場景中使用。
(4)SRCU通知鏈
定義如下:
SRCU通知鏈?zhǔn)峭ㄟ^Linux內(nèi)核中的SRCU(Synchronize RCUs)機制來實現(xiàn)的。SRCU通知鏈提供了更高級的同步機制,以確保在刪除或釋放通知處理器時,不會出現(xiàn)競態(tài)條件。這允許在通知鏈上安全地添加和刪除處理器。SRCU通知鏈的應(yīng)用場景包括網(wǎng)絡(luò)設(shè)備事件通知,其中多個處理器可能對事件做出響應(yīng),并且需要在處理器安全刪除時保持同步。
3、原理分析和API
(1)注銷通知器
在使用通知鏈之前,需要創(chuàng)建對應(yīng)類型的通知鏈,并使用注冊進行注冊,從源碼角度,每種類型的通知鏈都一一對應(yīng)著一個注冊函數(shù):
原子通知鏈注冊函數(shù):int atomic_notifier_chain_register(struct atomic_notifier_head *nh,struct notifier_block *nb)。
阻塞通知鏈注冊函數(shù):int atomic_notifier_chain_register(struct blocking_notifier_head *nh,struct notifier_block *nb)。
原始通知鏈注冊函數(shù):int atomic_notifier_chain_register(struct raw_notifier_head *nh,struct notifier_block *nb)。
srcu通知鏈注冊函數(shù):int atomic_notifier_chain_register(struct srcu_notifier_head *nh,struct notifier_block *nb)。
上述四種類型的注冊函數(shù)本質(zhì)上是調(diào)用notifier_chain_register()函數(shù)實現(xiàn)核心功能,該函數(shù)實現(xiàn)如下:
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; }
上述代碼是一個根據(jù)優(yōu)先級進行循環(huán)遍歷的操作,如果n的優(yōu)先級比*nl的優(yōu)先級高那么循環(huán)結(jié)束,接著就將n插入到*nl的前面。形成通知鏈。
(2)注銷通知器
有注冊函數(shù),則對應(yīng)著注銷函數(shù),四種通知鏈的注銷函數(shù)是:
原子通知鏈注銷函數(shù):int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh,struct notifier_block *nb);
阻塞通知鏈注銷函數(shù):int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh,struct notifier_block *nb);
原始通知鏈注銷函數(shù):int raw_notifier_chain_unregister(struct raw_notifier_head *nh,struct notifier_block *nb);
srcu通知鏈注銷函數(shù):int srcu_notifier_chain_unregister(struct srcu_notifier_head *nh,struct notifier_block *nb);
上述四種類型的注冊函數(shù)本質(zhì)上是調(diào)用notifier_chain_unregister()函數(shù)實現(xiàn)核心功能,該函數(shù)實現(xiàn)如下:
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; }
循環(huán)判斷找到了要注銷的然后執(zhí)行注銷,將其從鏈表中移除。
(3)通知鏈的通知
通常,通知鏈的注冊是由各個模塊在內(nèi)核初始化階段進行的。當(dāng)特定事件發(fā)生時,內(nèi)核會調(diào)用相應(yīng)的notifier_call_chain()函數(shù),以通知所有注冊的模塊或組件。這樣,不同的模塊可以根據(jù)事件類型和參數(shù)進行自定義處理,而無需顯式地知道其他模塊的存在。
四種通知鏈分別對應(yīng)不同的函數(shù):
原子通知鏈通知函數(shù):int atomic_notifier_call_chain(struct atomic_notifier_head *nh,unsigned long val, void *v);
阻塞通知鏈通知函數(shù):int blocking_notifier_call_chain(struct blocking_notifier_head *nh,unsigned long val, void *v);
原始通知鏈通知函數(shù):int raw_notifier_call_chain(struct raw_notifier_head *nh, unsigned long val, void *v);
srcu通知鏈通知函數(shù):int srcu_notifier_call_chain(struct srcu_notifier_head *nh, unsigned long val, void *v);
上述四個函數(shù)最后都會調(diào)用notifier_call_chain()實現(xiàn)核心功能,該函數(shù)實現(xiàn)如下:
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:指向通知鏈頭的指針。這是一個指向指針的指針,指向通知鏈的頭節(jié)點。
val:事件類型。鏈本身標(biāo)識的一組事件,val明確標(biāo)識一種事件類型
v:一個指針,指向攜帶更多事件相關(guān)信息的數(shù)據(jù)結(jié)構(gòu)。
nr_to_call:記錄發(fā)送的通知數(shù)量。如果不需要這個字段的值可以是NULL
nr_calls:通知程序調(diào)用鏈返回最后一個被調(diào)用的通知程序函數(shù)返回的值。
在notifier_chain_unregister()的while循環(huán)結(jié)構(gòu)中會調(diào)用:
ret=nb->notifier_call(nb,val,v);
依次執(zhí)行注冊到該通知鏈中的所有函數(shù)。
4、實例代碼
本小節(jié)通過原子通知鏈給出實例代碼,原子通知鏈可用于實現(xiàn)觀察者模式的通信機制。
(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)實現(xiàn)觀察者模塊
/** *模塊1,用于創(chuàng)建通知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); //定義模塊相關(guān)信息 MODULE_AUTHOR("iriczhao"); MODULE_LICENSE("GPL");
(3)事件發(fā)生模塊
/* *事件通知模塊 */ #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); //定義模塊相關(guān)信息 MODULE_AUTHOR("iriczhao"); MODULE_LICENSE("GPL");
(4)輸出結(jié)果
將上述三份代碼以模塊方式構(gòu)建,并加載進內(nèi)核,首先加載自定義的通知鏈my_notifier_list,接著加載module_1.ko注冊兩個事件訂閱者,最后加載module_2.ko通知事件,并向module_1發(fā)送兩個參數(shù):action和data,并通過module_1打印出來。輸出結(jié)果如下:
5、總結(jié)
本文描述了內(nèi)核的通知鏈機制并對其進行了簡單的實踐,加深了對內(nèi)核通知鏈的理解,方便對內(nèi)核中基于通知鏈設(shè)計的代碼的執(zhí)行行為的把控。
審核編輯:劉清
-
處理器
+關(guān)注
關(guān)注
68文章
19349瀏覽量
230312 -
LINUX內(nèi)核
+關(guān)注
關(guān)注
1文章
316瀏覽量
21678 -
API接口
+關(guān)注
關(guān)注
1文章
84瀏覽量
10467 -
RAW
+關(guān)注
關(guān)注
0文章
21瀏覽量
3815
原文標(biāo)題:接著整!玩一玩linux內(nèi)核的通知鏈
文章出處:【微信號:嵌入式小生,微信公眾號:嵌入式小生】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論