0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

玩一玩linux内核的通知链

嵌入式小生 来源:嵌入式小生 2023-07-06 09:05 次阅读

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,内核开发人员可以更好地设计模块化和可扩展的系统,让不同的组件以解耦的方式对事件做出响应。这种模式有助于更好地组织代码,并且在不影响现有代码的情况下更容易添加新功能到内核中。

整个结构如下图所示:

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

2、通知链的类型

在linux内核中,定义了四种类型的通知链。

(1)原子(Atomic)通知链

定义如下:

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

原子通知链在内核中广泛应用,特别是在一些基本的通知机制中。这种通知链的处理是原子的,意味着在处理链上的通知时,不会被中断或其他并发操作干扰。原子通知链的应用场景包括进程退出通知、进程停止通知、以及内核调试和跟踪事件通知等。

(2)阻塞(Block)通知链

定义如下:

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

阻塞通知链用于一些需要等待通知链中所有处理器完成后才能继续执行的场景。当某个处理器在链上发起通知后,阻塞通知链将等待所有处理器都完成其任务后才返回。阻塞通知链的应用场景包括内核模块的初始化,其中一个模块可能需要等待其他模块完成初始化后才能继续执行。

(3)原始(RAW)通知链

定义如下:

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

原始通知链是一种特殊类型的通知链,它没有任何同步机制。这意味着在处理通知链时,不进行任何锁定或同步操作,这可能会导致并发问题。原始通知链主要用于一些低层的底层通知机制,通常需要使用者自己确保线程安全性。原始通知链的应用场景相对较少,可能只在一些特定的高性能场景中使用

(4)SRCU通知链

定义如下:

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

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打印出来。输出结果如下:

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

5、总结

本文描述了内核的通知链机制并对其进行了简单的实践,加深了对内核通知链的理解,方便对内核中基于通知链设计的代码的执行行为的把控。





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 处理器
    +关注

    关注

    68

    文章

    18275

    浏览量

    222158
  • LINUX内核
    +关注

    关注

    1

    文章

    311

    浏览量

    21389
  • API接口
    +关注

    关注

    1

    文章

    79

    浏览量

    10314
  • RAW
    RAW
    +关注

    关注

    0

    文章

    20

    浏览量

    3748

原文标题:接着整!玩一玩linux内核的通知链

文章出处:【微信号:嵌入式小生,微信公众号:嵌入式小生】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    430的好的大神没

    有米有430的好的,求助。谢谢362652686@qq.com。必须有重谢。
    发表于 06-21 17:40

    PSP***典

    PSP***典
    发表于 08-16 16:31

    用51彩屏

    51彩屏,程序太大怎么办?
    发表于 08-20 16:31

    教你USB与RAM书

    教你USB与RAM书,通信
    发表于 04-23 09:10

    想DIY个门禁系统,就是不会开发软体

    想DIY个门禁系统,想了想主要就是不会开发软体~~~~(>_
    发表于 06-03 14:35

    【小狂系列】跟着小狂cc3200--导航篇

    本帖最后由 3guoyangyang7 于 2016-10-18 19:35 编辑 哈哈,TI的各位大大们,小狂来cc3200搅搅场子,嘻嘻,先来篇导航篇吊吊胃口,哈哈,会根据实际的需求来做
    发表于 05-01 11:52

    手掌控的酷

    `迷你如此轻巧如此任性遥控,潜水随拍要就要手掌控我是水下无人机,别问我防水如何,别说我潜水如何,作为条鱼,条现代科技的鱼,玩水,
    发表于 01-10 18:02

    如何USB PDF

    有用到的可以看下。。圈圈教你USB----PDF: http://pan.baidu.com/s/1ntoKSwL
    发表于 07-11 02:16

    用STM32L298N相关资料推荐

    )/STM32F103C8T6小板硬件:L298N,网上搜就有软件:Keil MDK5.29买L298N回来的时候,给的例程是51的,我手上又没有这种板子,解决方法就是自己在网上找别人怎么做,找不到就去找原理图,慢慢来,肯定会有收获的!开发板图片(正点原子F103核心板)[外
    发表于 06-29 06:02

    教你STM32

    源:STM32视频教程《原子教你STM32》转载
    发表于 08-03 06:19

    嵌入式Linux之异步通知方式问题汇总

    功能介绍所谓同步,就是“你慢我等你”。那么异步就是:你慢那你就自己,我做自己的事去了,有情况再通知我。所谓异步通知,就是 APP 可以忙自己的事,当驱动程序用数据时它会主动给 APP 发信号,这会
    发表于 11-04 07:10

    旋转编码器模块

    旋转编码器模块我是代码小白,个正在做毕设的秃头少年。鄙人拙作,有不当之处,还请指教。正文毕业设计已经OK啦,但是买的很多传感器都没用上,现在工作之余
    发表于 01-05 07:22

    史上最强板子姿势—【平头哥】云上实验室 “云芯片”

    云上开发,颠覆你的开发习惯,远程实时评估,亲临实验室的体验!【平头哥】进入云上实验室去 “云上芯片”作为资深开发板或者芯片的同学,想必传统玩法都是:"台电脑+USB线+板子+各种
    发表于 03-09 06:18

    Linux内核通知链如何引入?原理是什么?如何使用和实现?及实例分析

    内核通知链引入 概念 1.子系统之间产生关联(耦合) 2.只能在内核子系统之间使用,不能内核与用户空间 3. 函数注册到一个链表,事件产生后调用链表上的函数
    发表于 09-12 15:05 3次下载
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b><b class='flag-5'>通知</b>链如何引入?原理是什么?如何使用和实现?及实例分析

    需要了解Linux内核通知链机制的原理及实现

    大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣。为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子系统,Linux内核提供了
    发表于 05-14 16:16 662次阅读
    需要了解<b class='flag-5'>Linux</b><b class='flag-5'>内核</b><b class='flag-5'>通知</b>链机制的原理及实现