0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

玩一玩linux內(nèi)核的通知鏈

嵌入式小生 ? 來(lái)源:嵌入式小生 ? 2023-07-06 09:05 ? 次閱讀

1、通知鏈簡(jiǎn)介

文本描述構(gòu)成通知鏈的具體數(shù)據(jù)結(jié)構(gòu)和API接口,同時(shí)描述四種通知鏈的具體應(yīng)用場(chǎng)景,并對(duì)API接口進(jìn)行簡(jiǎn)要分析。

Linux內(nèi)核中,struct notifier_block 是一種數(shù)據(jù)結(jié)構(gòu),用于實(shí)現(xiàn)觀察者模式。它允許內(nèi)核的不同部分將自己注冊(cè)為監(jiān)聽(tīng)器(觀察者)以偵聽(tīng)特定事件。當(dāng)這些事件發(fā)生時(shí),內(nèi)核會(huì)通知所有注冊(cè)的notifier block,它們可以對(duì)事件做出適當(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:這個(gè)字段指向在通知事件發(fā)生時(shí)將被調(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:這個(gè)字段是指向鏈中下一個(gè) notifier block 的指針。Linux內(nèi)核維護(hù)一個(gè)已注冊(cè)的 notifier block 的鏈表,該字段使得可以遍歷整個(gè)鏈表。

priority:這個(gè)字段決定了該 notifier block 相對(duì)于其他已注冊(cè) notifier block 的優(yōu)先級(jí)。當(dāng)多個(gè)塊為同一事件注冊(cè)時(shí),內(nèi)核按照優(yōu)先級(jí)降序通知它們。具有較高優(yōu)先級(jí)值的 notifier block 將在具有較低優(yōu)先級(jí)值的之前收到通知。

要使用 struct notifier_block,內(nèi)核模塊可以使用Linux內(nèi)核提供的函數(shù)進(jìn)行注冊(cè),例如register_inotifier() 或 register_netdevice_notifier(),具體取決于特定的事件類別。

一些常見(jiàn)的利用 struct notifier_block 的事件包括:

文件系統(tǒng)事件,如文件創(chuàng)建、刪除和修改。

網(wǎng)絡(luò)設(shè)備事件,如接口的啟用或禁用。

內(nèi)存管理事件,如頁(yè)面分配和釋放。

通過(guò)使用 struct notifier_block,內(nèi)核開(kāi)發(fā)人員可以更好地設(shè)計(jì)模塊化和可擴(kuò)展的系統(tǒng),讓不同的組件以解耦的方式對(duì)事件做出響應(yīng)。這種模式有助于更好地組織代碼,并且在不影響現(xiàn)有代碼的情況下更容易添加新功能到內(nèi)核中。

整個(gè)結(jié)構(gòu)如下圖所示:

fc8ed7f0-1b91-11ee-962d-dac502259ad0.png

2、通知鏈的類型

在linux內(nèi)核中,定義了四種類型的通知鏈。

(1)原子(Atomic)通知鏈

定義如下:

fcb3bab6-1b91-11ee-962d-dac502259ad0.png

原子通知鏈在內(nèi)核中廣泛應(yīng)用,特別是在一些基本的通知機(jī)制中。這種通知鏈的處理是原子的,意味著在處理鏈上的通知時(shí),不會(huì)被中斷或其他并發(fā)操作干擾。原子通知鏈的應(yīng)用場(chǎng)景包括進(jìn)程退出通知、進(jìn)程停止通知、以及內(nèi)核調(diào)試和跟蹤事件通知等。

(2)阻塞(Block)通知鏈

定義如下:

fcd05022-1b91-11ee-962d-dac502259ad0.png

阻塞通知鏈用于一些需要等待通知鏈中所有處理器完成后才能繼續(xù)執(zhí)行的場(chǎng)景。當(dāng)某個(gè)處理器在鏈上發(fā)起通知后,阻塞通知鏈將等待所有處理器都完成其任務(wù)后才返回。阻塞通知鏈的應(yīng)用場(chǎng)景包括內(nèi)核模塊的初始化,其中一個(gè)模塊可能需要等待其他模塊完成初始化后才能繼續(xù)執(zhí)行。

(3)原始(RAW)通知鏈

定義如下:

fce4bddc-1b91-11ee-962d-dac502259ad0.png

原始通知鏈?zhǔn)且环N特殊類型的通知鏈,它沒(méi)有任何同步機(jī)制。這意味著在處理通知鏈時(shí),不進(jìn)行任何鎖定或同步操作,這可能會(huì)導(dǎo)致并發(fā)問(wèn)題。原始通知鏈主要用于一些低層的底層通知機(jī)制,通常需要使用者自己確保線程安全性。原始通知鏈的應(yīng)用場(chǎng)景相對(duì)較少,可能只在一些特定的高性能場(chǎng)景中使用。

(4)SRCU通知鏈

定義如下:

fcf58b12-1b91-11ee-962d-dac502259ad0.png

SRCU通知鏈?zhǔn)峭ㄟ^(guò)Linux內(nèi)核中的SRCU(Synchronize RCUs)機(jī)制來(lái)實(shí)現(xiàn)的。SRCU通知鏈提供了更高級(jí)的同步機(jī)制,以確保在刪除或釋放通知處理器時(shí),不會(huì)出現(xiàn)競(jìng)態(tài)條件。這允許在通知鏈上安全地添加和刪除處理器。SRCU通知鏈的應(yīng)用場(chǎng)景包括網(wǎng)絡(luò)設(shè)備事件通知,其中多個(gè)處理器可能對(duì)事件做出響應(yīng),并且需要在處理器安全刪除時(shí)保持同步。

3、原理分析和API

(1)注銷通知器

在使用通知鏈之前,需要?jiǎng)?chuàng)建對(duì)應(yīng)類型的通知鏈,并使用注冊(cè)進(jìn)行注冊(cè),從源碼角度,每種類型的通知鏈都一一對(duì)應(yīng)著一個(gè)注冊(cè)函數(shù):

原子通知鏈注冊(cè)函數(shù):int atomic_notifier_chain_register(struct atomic_notifier_head *nh,struct notifier_block *nb)。

阻塞通知鏈注冊(cè)函數(shù):int atomic_notifier_chain_register(struct blocking_notifier_head *nh,struct notifier_block *nb)。

原始通知鏈注冊(cè)函數(shù):int atomic_notifier_chain_register(struct raw_notifier_head *nh,struct notifier_block *nb)。

srcu通知鏈注冊(cè)函數(shù):int atomic_notifier_chain_register(struct srcu_notifier_head *nh,struct notifier_block *nb)。

上述四種類型的注冊(cè)函數(shù)本質(zhì)上是調(diào)用notifier_chain_register()函數(shù)實(shí)現(xiàn)核心功能,該函數(shù)實(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;
}

上述代碼是一個(gè)根據(jù)優(yōu)先級(jí)進(jìn)行循環(huán)遍歷的操作,如果n的優(yōu)先級(jí)比*nl的優(yōu)先級(jí)高那么循環(huán)結(jié)束,接著就將n插入到*nl的前面。形成通知鏈。

(2)注銷通知器

有注冊(cè)函數(shù),則對(duì)應(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);

上述四種類型的注冊(cè)函數(shù)本質(zhì)上是調(diào)用notifier_chain_unregister()函數(shù)實(shí)現(xiàn)核心功能,該函數(shù)實(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)通知鏈的通知

通常,通知鏈的注冊(cè)是由各個(gè)模塊在內(nèi)核初始化階段進(jìn)行的。當(dāng)特定事件發(fā)生時(shí),內(nèi)核會(huì)調(diào)用相應(yīng)的notifier_call_chain()函數(shù),以通知所有注冊(cè)的模塊或組件。這樣,不同的模塊可以根據(jù)事件類型和參數(shù)進(jìn)行自定義處理,而無(wú)需顯式地知道其他模塊的存在。

四種通知鏈分別對(duì)應(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);

上述四個(gè)函數(shù)最后都會(huì)調(diào)用notifier_call_chain()實(shí)現(xiàn)核心功能,該函數(shù)實(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:指向通知鏈頭的指針。這是一個(gè)指向指針的指針,指向通知鏈的頭節(jié)點(diǎn)。

val:事件類型。鏈本身標(biāo)識(shí)的一組事件,val明確標(biāo)識(shí)一種事件類型

v:一個(gè)指針,指向攜帶更多事件相關(guān)信息的數(shù)據(jù)結(jié)構(gòu)。

nr_to_call:記錄發(fā)送的通知數(shù)量。如果不需要這個(gè)字段的值可以是NULL

nr_calls:通知程序調(diào)用鏈返回最后一個(gè)被調(diào)用的通知程序函數(shù)返回的值。

在notifier_chain_unregister()的while循環(huán)結(jié)構(gòu)中會(huì)調(diào)用:

ret=nb->notifier_call(nb,val,v);

依次執(zhí)行注冊(cè)到該通知鏈中的所有函數(shù)。

4、實(shí)例代碼

本小節(jié)通過(guò)原子通知鏈給出實(shí)例代碼,原子通知鏈可用于實(shí)現(xiàn)觀察者模式的通信機(jī)制。

(1)定義一個(gè)通知鏈

#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);



//向通知鏈注冊(cè)通知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)實(shí)現(xiàn)觀察者模塊

/**
*模塊1,用于創(chuàng)建通知block,并注冊(cè)
*/
#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)建,并加載進(jìn)內(nèi)核,首先加載自定義的通知鏈my_notifier_list,接著加載module_1.ko注冊(cè)兩個(gè)事件訂閱者,最后加載module_2.ko通知事件,并向module_1發(fā)送兩個(gè)參數(shù):action和data,并通過(guò)module_1打印出來(lái)。輸出結(jié)果如下:

fd078f60-1b91-11ee-962d-dac502259ad0.png

5、總結(jié)

本文描述了內(nèi)核的通知鏈機(jī)制并對(duì)其進(jìn)行了簡(jiǎn)單的實(shí)踐,加深了對(duì)內(nèi)核通知鏈的理解,方便對(duì)內(nèi)核中基于通知鏈設(shè)計(jì)的代碼的執(zhí)行行為的把控。





審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 處理器
    +關(guān)注

    關(guān)注

    68

    文章

    19048

    瀏覽量

    228528
  • LINUX內(nèi)核
    +關(guān)注

    關(guān)注

    1

    文章

    316

    瀏覽量

    21583
  • API接口
    +關(guān)注

    關(guān)注

    1

    文章

    81

    瀏覽量

    10415
  • RAW
    RAW
    +關(guān)注

    關(guān)注

    0

    文章

    21

    瀏覽量

    3783

原文標(biāo)題:接著整!玩一玩linux內(nèi)核的通知鏈

文章出處:【微信號(hào):嵌入式小生,微信公眾號(hào):嵌入式小生】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    如何在Linux終端上安裝和經(jīng)典的貪吃蛇游戲

    本文就是如何在 Linux 終端上安裝和經(jīng)典的貪吃蛇游戲。
    發(fā)表于 10-17 09:48 ?1669次閱讀

    430的好的大神沒(méi)

    有米有430的好的,求助。謝謝362652686@qq.com。必須有重謝。
    發(fā)表于 06-21 17:40

    PSP***典

    PSP***典
    發(fā)表于 08-16 16:31

    想DIY個(gè)門禁系統(tǒng),就是不會(huì)開(kāi)發(fā)軟體

    想DIY個(gè)門禁系統(tǒng),想了想主要就是不會(huì)開(kāi)發(fā)軟體~~~~(>_
    發(fā)表于 06-03 14:35

    個(gè)手掌控的酷

    `迷你如此輕巧如此任性遙控,潛水隨拍要就要手掌控我是水下無(wú)人機(jī),別問(wèn)我防水如何,別說(shuō)我潛水如何,作為條魚(yú),條現(xiàn)代科技的魚(yú),玩水,
    發(fā)表于 01-10 18:02

    嵌入式Linux之異步通知方式問(wèn)題匯總

    功能介紹所謂同步,就是“你慢我等你”。那么異步就是:你慢那你就自己,我做自己的事去了,有情況再通知我。所謂異步通知,就是 APP 可以忙自己的事,當(dāng)驅(qū)動(dòng)程序用數(shù)據(jù)時(shí)它會(huì)主動(dòng)給 APP 發(fā)信號(hào),這會(huì)
    發(fā)表于 11-04 07:10

    旋轉(zhuǎn)編碼器模塊

    旋轉(zhuǎn)編碼器模塊我是代碼小白,個(gè)正在做畢設(shè)的禿頭少年。鄙人拙作,有不當(dāng)之處,還請(qǐng)指教。正文畢業(yè)設(shè)計(jì)已經(jīng)OK啦,但是買的很多傳感器都沒(méi)用上,現(xiàn)在工作之余
    發(fā)表于 01-05 07:22

    榮耀暢7X和暢6X哪個(gè)好?對(duì)比測(cè)評(píng)

    從升級(jí)角度來(lái)說(shuō),榮耀暢7X相比榮耀暢6X主要升級(jí)了外觀顏值、性能等多個(gè)方面,相當(dāng)于有了個(gè)比較全面的升級(jí),體驗(yàn)上無(wú)疑更為出色,從體驗(yàn)上來(lái)講,今后首選自然是榮耀暢7X,畢竟新款機(jī)型
    發(fā)表于 12-11 17:10 ?6534次閱讀

    Linux內(nèi)核通知如何引入?原理是什么?如何使用和實(shí)現(xiàn)?及實(shí)例分析

    內(nèi)核通知引入 概念 1.子系統(tǒng)之間產(chǎn)生關(guān)聯(lián)(耦合) 2.只能在內(nèi)核子系統(tǒng)之間使用,不能內(nèi)核與用戶空間 3. 函數(shù)注冊(cè)到
    發(fā)表于 09-12 15:05 ?3次下載
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>通知</b><b class='flag-5'>鏈</b>如何引入?原理是什么?如何使用和實(shí)現(xiàn)?及實(shí)例分析

    榮耀暢7X怎么樣

    榮耀暢系列直游走在千元機(jī)的定位,帶給人們實(shí)惠。所包含的的A、C、X系列可能會(huì)讓你迷惑,其實(shí)按照字母排序解讀就好。沒(méi)錯(cuò)!X系列是暢里的高端,就比如本篇將介紹的榮耀暢7X。
    的頭像 發(fā)表于 03-08 09:12 ?3344次閱讀

    需要了解Linux內(nèi)核通知機(jī)制的原理及實(shí)現(xiàn)

    大多數(shù)內(nèi)核子系統(tǒng)都是相互獨(dú)立的,因此某個(gè)子系統(tǒng)可能對(duì)其它子系統(tǒng)產(chǎn)生的事件感興趣。為了滿足這個(gè)需求,也即是讓某個(gè)子系統(tǒng)在發(fā)生某個(gè)事件時(shí)通知其它的子系統(tǒng),Linux內(nèi)核提供了
    發(fā)表于 05-14 16:16 ?760次閱讀
    需要了解<b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>通知</b><b class='flag-5'>鏈</b>機(jī)制的原理及實(shí)現(xiàn)

    什么是客幣用戶怎樣可以獲得客幣

    客幣是基于“OneCloud客云”智能硬件,依托共享經(jīng)濟(jì)云計(jì)算和區(qū)塊技術(shù)的數(shù)字資產(chǎn)。 作為種資源兌換媒介,用戶可以通過(guò)客幣獲
    發(fā)表于 06-08 09:00 ?5888次閱讀

    客云是什么_客云怎么掙錢

    本文首先介紹了客云的概念,其次介紹了客云的作用功能,最后介紹了客云的掙錢方法。
    發(fā)表于 05-08 09:37 ?1.1w次閱讀

    客云挖礦入門教程

    客云挖礦換句話說(shuō)就是通過(guò)客云共享家庭閑置的帶寬和存儲(chǔ),獲得客幣獎(jiǎng)勵(lì)回報(bào),那么怎么呢?
    發(fā)表于 05-08 09:44 ?9044次閱讀
    <b class='flag-5'>玩</b>客云挖礦入門教程

    如何在LinuxGOG游戲

    了解如何在 Linux GOG 游戲,將您的 Linux 桌面變成個(gè)成熟的游戲系統(tǒng)。 Linux 上的游戲在過(guò)去幾年中取得了長(zhǎng)足的進(jìn)
    的頭像 發(fā)表于 01-17 09:50 ?2100次閱讀