Linux内核基础 | 通知链机制
一、通知链简介
举个形象的例子:将通知链比喻成”订阅者-发布者“,订阅者将感兴趣的公众号关注并设置提醒,发布者一旦发布某个文章,订阅者即可收到通知看到发布的内容。
在Linux内核中为了及时响应某些到来的事件,采取了通知链机制。该机制的两个角色的任务:
1、通知者定义通知链
2、被通知者向通知链中注册回调函数
3、当事件发生时,通知者发送通知 (执行通知链上每个调用块上的回调函数)所以通知链是一个单链表,单链表上的节点是调用块,每个调用块上有事件相关的回调函数和调用块的优先级。当事件触发时会按优先级顺序执行该链表上的回调函数。通知链只用于各个子系统之间,不能用于内核和用户空间进行事件的通知。
二、相关细节
1、通知链的类型
原子通知链( Atomic notifier chains ):
通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,不允许阻塞。
可阻塞通知链( Blocking notifier chains ):
通知链元素的回调函数在进程上下文中运行,允许阻塞。
原始通知链( Raw notifier chains ):
对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。
SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体
本文将以原子通知链进行分析
【文章福利】小编推荐自己的Linux内核技术交流群:【891587639】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!(含视频教程、电子书、实战项目及代码)


2、原子通知链与通知块
初始化一个原子通知链使用以下宏定义
例如创建一个设备通知链队列头:
struct raw_notifier_head就相当于存放这条通知链单链表头,每一个通知链上的元素也就是通知块如下定义:
回调函数接口:
整个通知链的组织如下图所示:

3、向通知链中插入通知块
4、调用通知链
三、编写内核模块进行实验
1、案例1
编写内核模块作为被通知者,向内核netdev_chain通知链中插入自定义通知块(在通知块中自定义事件触发的回调函数),源码如下:
Makefile:
将模块插入内核后,将网卡关闭再重启一次,查看日志信息:
2、案例2
通过写两个内核模块,其中一个作为通知者一个作为被通知者
module_1.c:
初始化一个通知链
定义事件的回调函数并向通知链中插入三个通知块(与之前定义的回调函数相对应)
测试通知链:循环遍历通知链的通知块,并同时调用对应的回调函数
module_2.c:模拟某事件发生,并调用通知链
(module_1与module_2的Makefile可参考上面的Demo1)
运行时先插入module_1再插入module_2结果如下,红框内是module_1中的测试输出日志,绿框内为世界调用通知链时的执行结果日志。

从上面可以看到通知链的执行顺序是按照优先级进行的,那么当调用通知链时是否每个通知块上的回调函数都会执行呢?
答案:不是,每个被执行的notifier_block回调函数的返回值可能取值以下几个:
NOTIFY_DONE:表示对相关的事件类型不关心。
NOTIFY_OK:顺利执行。
NOTIFY_BAD:执行有错。
NOTIFY_STOP:停止执行后面的回调函数。
NOTIFY_STOP_MASK:停止执行的掩码
如当返回值NOTIFY_STOP_MASK会停止执行后面优先级低的调用块的函数。
例如把module_1中通知块的回调函数B_call的返回值修改为NOTIFY_STOP_MASK后,重新编译,运行结果如下,只执行了调用链中调用块2的回调函数。

