【linux kernel】一文总结linux内核通知链
文章目录
- 1、通知链简介
- 2、通知链的类型
- 3、原理分析和API
- (1)注销通知器
- (2)注销通知器
- (3)通知链的通知
- 4、实例代码
- (1)定义一个通知链
- (2)实现观察者模块
- (3)事件发生模块
- (4)输出结果
内核源码中相关文件
- /kernel/notifier.c
- /include/linux/notifier.h
1、通知链简介
文本基于内核源码
4.19.4描述构成通知链的具体数据结构和API接口,同时描述四种通知链的具体应用场景,并对API接口进行简要分析。
在Linux内核中,struct notifier_block 是一种数据结构,用于实现观察者模式。它允许内核的不同部分将自己注册为监听器(观察者)以侦听特定事件。当这些事件发生时,内核会通知所有注册的notifier block,它们可以对事件做出适当的响应。
struct notifier_block 在Linux内核头文件 include/linux/notifier.h 中定义,并具有以下结构:
struct notifier_block {int (*notifier_call)(struct notifier_block *nb, unsigned long action, void *data);struct notifier_block *next;int priority;
};
-
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()函数实现核心功能,该函数实现如下:
static int notifier_chain_register(struct notifier_block **nl,struct notifier_block *n)
{while ((*nl) != NULL) {if (n->priority > (*nl)->priority)break;nl = &((*nl)->next);}n->next = *nl;rcu_assign_pointer(*nl, n);return 0;
}
上述代码是一个根据优先级进行循环遍历的操作,如果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()函数实现核心功能,该函数实现如下:
static int notifier_chain_unregister(struct notifier_block **nl,struct notifier_block *n)
{while ((*nl) != NULL) {if ((*nl) == n) {rcu_assign_pointer(*nl, n->next);return 0;}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()实现核心功能,该函数实现如下:
static int notifier_call_chain(struct notifier_block **nl,unsigned long val, void *v,int nr_to_call, int *nr_calls)
{int ret = NOTIFY_DONE;struct notifier_block *nb, *next_nb;nb = rcu_dereference_raw(*nl);while (nb && nr_to_call) {next_nb = rcu_dereference_raw(nb->next);#ifdef CONFIG_DEBUG_NOTIFIERSif (unlikely(!func_ptr_is_kernel_text(nb->notifier_call))) {WARN(1, "Invalid notifier called!");nb = next_nb;continue;}
#endifret = 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--;}return ret;
}
- nl:指向通知链头的指针。这是一个指向指针的指针,指向通知链的头节点。
- val:事件类型。链本身标识的一组事件,val明确标识一种事件类型
- v:一个指针,指向携带更多事件相关信息的数据结构。
- nr_to_call:记录发送的通知数量。如果不需要这个字段的值可以是NULL
- nr_calls:通知程序调用链返回最后一个被调用的通知程序函数返回的值。
在notifier_chain_unregister()的while循环结构中会调用:
ret = nb->notifier_call(nb, val, v);
依次执行注册到该通知链中的所有函数。
4、实例代码
本小节通过原子通知链给出实例代码,原子通知链可用于实现观察者模式的通信机制。
(1)定义一个通知链
#include <linux/notifier.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() *///定义原子通知链
static ATOMIC_NOTIFIER_HEAD(my_notifier_list);//通知事件
static int call_notifiers(unsigned long val, void *v)
{return atomic_notifier_call_chain(&my_notifier_list, val, v);}
EXPORT_SYMBOL(call_notifiers);//向通知链注册通知block
static int register_notifier(struct notifier_block *nb)
{int err;err = atomic_notifier_chain_register(&my_notifier_list, nb);if(err)return err;
}
EXPORT_SYMBOL(register_notifier);//从通知链中注销通知block
static int unregister_notifier(struct notifier_block *nb)
{int err;err = atomic_notifier_chain_unregister(&my_notifier_list, nb);if(err)return err;
}
EXPORT_SYMBOL(unregister_notifier);static int __init myNotifier_init(void)
{printk("myNotifier init finish\n");return 0;
}static void __exit myNotifier_exit(void)
{printk("myNotifier exit finish\n");
}module_init(myNotifier_init);
module_exit(myNotifier_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("iriczhao");
(2)实现观察者模块
/*** 模块1,用于创建通知block,并注册
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/notifier.h>extern int register_notifier(struct notifier_block *nb);
extern int unregister_notifier(struct notifier_block *nb);static int notifier_one_call_fn(struct notifier_block *nb,unsigned long action, void *data)
{printk(">>this is notifier_one_call_fn\n");printk("recv action = %d data = %p\n",action,data);return 0;
}static int notifier_two_call_fn(struct notifier_block *nb,unsigned long action, void *data)
{printk(">>this is notifier_two_call_fn\n");return 0;
}/* define a notifier_block */
static struct notifier_block notifier_one = {.notifier_call = notifier_one_call_fn,
};static struct notifier_block notifier_two = {.notifier_call = notifier_two_call_fn,
};static int __init module_1_init(void)
{register_notifier(¬ifier_two);register_notifier(¬ifier_one);return 0;
}static void __exit module_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 <linux/init.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/kernel.h>extern int call_notifiers(unsigned long val, void *v);static int event_module_init(void)
{printk("Event module initialized\n");unsigned long event = 123;void *data = (void *)0xDEADBEEF;call_notifiers(event, data);return 0;
}static void event_module_exit(void)
{printk("Event module exiting\n");
}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打印出来。输出结果如下:

相关文章:
【linux kernel】一文总结linux内核通知链
文章目录 1、通知链简介2、通知链的类型3、原理分析和API(1)注销通知器(2)注销通知器(3)通知链的通知 4、实例代码(1)定义一个通知链(2)实现观察者模块&#…...
kafka入门,Kafka 副本(十三)
Kafka副本 副本基本信息 1)Kafka副本作用,提高数据可靠性 2)Kafka默认副本1个,生产环境一般配置2个,保证数据可靠性,太多副本会增加磁盘存储空间,增加网络上数据传输,降低效率 3&a…...
利用PPT制作简单的矢量图
1.用PPT画一个图形(可以多个图) 2.鼠标圈住图形 3.利用 Ctrl G 组合图形,再用 Ctrl C 复制 4.打开word—粘贴—选择性粘贴—图片(增强性图元文件) 确认即可。...
18-Linux 常用命令
目录 1.ls PS:FinalShell设置背景和字体 2.pwd 3.cd PS:认识 Linux 目录结构——Linux 是一个树形目录结构 PS:绝对路径 vs 相对路径 PS:使用 tab 键补全 PS:使用 ctrl c 重新输入 4.touch PS:L…...
2024考研408-计算机组成原理第六章-总线学习笔记
文章目录 前言初识总线一、总线概述1.1、总线的概述1.1.1、认识总线1.1.2、设计总线需要的特性1.1.3、总线的分类①按照数据传输格式分(串行、并行)②按照总线功能连接的总线(片内总线、系统总线、通信总线)③按照时序控制方式&am…...
uni_app 微信小程序 苹果手机 边框显示不全
 1.1 配置vue.config.js 1.2 axios 使用 1.3 终端打印 2 第二种方法 --> 错误 --> 没有运行成功 2.1 配置vue.config.js --> 就是api 不被设置成 替换为 / 2.2 axios 使用…...
使用gradio库的File模块实现文件上传和展示
❤️觉得内容不错的话,欢迎点赞收藏加关注😊😊😊,后续会继续输入更多优质内容❤️ 👉有问题欢迎大家加关注私戳或者评论(包括但不限于NLP算法相关,linux学习相关,读研读博…...
网络安全进阶学习第四课——SSRF服务器请求伪造
文章目录 一、什么是SSRF?二、SSRF成因三、SSRF简析四、PHP存在SSRF的风险函数五、后台源码获取方式六、SSRF危害七、SSRF漏洞挖掘从WEB功能上寻找,从URL关键字中寻找 八、SSRF具体利用ssrf常利用的相关协议PHP伪协议读取文件端口扫描 九、SSRF存在的必要…...
js处理扁平数组和树结构相互转换
一、将扁平的数据转为树形结构 在 js中,可以使用递归算法将扁平的数据转换为树形结构。 扁平数据通常是一个带有 parentId 属性的数组,而树形结构通常是一个带有 children 属性的对象。 1、方法一 下面是一个简单的例子,演示如何将扁平数…...
Spark弹性分布式数据集
1. Spark RDD是什么 RDD(Resilient Distributed Dataset,弹性分布式数据集)是一个不可变的分布式对象集合,是Spark中最基本的数据抽象。在代码中RDD是一个抽象类,代表一个弹性的、不可变、可分区、里面的元素可并行计…...
ffmpeg学习记录
1、对图片进行裁剪 ffmpeg -i input.jpg -vf cropiw/3:ih:20:0 caijian.jpg PS: crop100:100:12:34 相同效果: cropw100:h100:x12:y34 2、视频增加文字水印 使用drawtext滤镜进行增加水印 参数 类型 说明 text 字符串 文字 textfile 字符串 文字文件 …...
ChatGPT:为教育创新提供五大机遇
随着智能技术的不断发展,ChatGPT在教育场景中的创新价值可能比我们能够意识到的还要多。比如它可以自动处理作业、在线答疑,可以辅助语言学习、实时沟通,甚至还可以用于评估诊断、科学研究。国内外关于利用ChatGPT实现教育创新的场景描绘已经…...
Educational Codeforces Round 151 (Rated for Div. 2)
Edu 151 A. Forbidden Integer 题意: 你有[1, k]内除了 x x x的整数,每个数可以拿多次,问 ∑ n \sum n ∑n是否可行并构造 思路: 有1必能构造,否则假如没有1,假如有2, 3必定能构造出大于等于2的所有数&…...
【AI机器学习入门与实战】机器学习算法都有哪些分类?
👍【AI机器学习入门与实战】目录 🍭基础篇 🔥 第一篇:【AI机器学习入门与实战】AI 人工智能介绍 🔥 第二篇:【AI机器学习入门与实战】机器学习核心概念理解 🔥 第三篇:【AI机器学习入…...
React之hooks
Hooks函数 1.useState():状态钩子。纯函数组件没有状态,用于为函数组件引入state状态, 并进行状态数据的读写操作。 const [state, setState] useState(initialValue); // state:初始的状态属性,指向状态当前值,类似…...
1.监控分布式--zabbix
文章目录 监控分布式-zabbix、prometheus概念工作原理功能组件部署zabbix安装Nginx和PHP环境部署数据库编码安装zabbix编译安装zabbix server客户端安装zabbix agent服务 监控分布式-zabbix、prometheus 利用一个优秀的监控软件,我们可以: 通过一个友好的界面进行…...
java stream 多个集合去重取交集
文章目录 背景案例代码 背景 原因是需要从表里查多个集合list,然后取多个集合得交集,并且元素是对象,所以使用了下面的方式,当然方式有很多种,仅供参考。 案例 下面提供了一段多个集合join取交集的例子,…...
给LLM装上知识:从LangChain+LLM的本地知识库问答到LLM与知识图谱的结合
第一部分 什么是LangChain:连接本地知识库与LLM的桥梁 作为一个 LLM 应用框架,LangChain 支持调用多种不同模型,提供相对统一、便捷的操作接口,让模型即插即用,这是其GitHub地址,其架构如下图所示 (点此查…...
视频与AI,与进程交互(二) pytorch 极简训练自己的数据集并识别
目标学习任务 检测出已经分割出的图像的分类 2 使用pytorch pytorch 非常简单就可以做到训练和加载 2.1 准备数据 如上图所示,用来训练的文件放在了train中,验证的文件放在val中,train.txt 和 val.txt 分别放文件名称和分类类别ÿ…...
别再让用户长按了!用html2canvas在微信H5里优雅生成分享海报(Vue3/TS实战)
微信H5海报生成实战:用html2canvas打造零摩擦分享体验 每次看到用户笨拙地长按屏幕、小心翼翼地调整手指位置就为了保存一张活动海报,作为开发者的你是否感到一丝愧疚?在移动端体验至上的今天,这种原始操作显然与"优雅"…...
LinkSwift网盘直链下载助手:2025年高效下载终极解决方案
LinkSwift网盘直链下载助手:2025年高效下载终极解决方案 【免费下载链接】Online-disk-direct-link-download-assistant 可以获取网盘文件真实下载地址。基于【网盘直链下载助手】修改(改自6.1.4版本) ,自用,去推广&am…...
如何从碎片化信息中构建系统性科研认知?
在科研工作中,我们常常面临这样一种困境:每天通过各种渠道接触到海量的学术信息,这些信息如同散落的拼图碎片,虽然珍贵,却难以自动拼凑成一幅完整的画面。对于许多科研人员而言,难以形成系统认知是一个巨大…...
如何高效提取与编辑Unity游戏资源?UABEA全功能解析与实践指南
如何高效提取与编辑Unity游戏资源?UABEA全功能解析与实践指南 【免费下载链接】UABEA UABEA: 这是一个用于新版本Unity的C# Asset Bundle Extractor(资源包提取器),用于提取游戏中的资源。 项目地址: https://gitcode.com/gh_mi…...
Comsol 多裂纹水力压裂扩展:拉伸与压缩下的破坏探索
comsol多裂纹水力压裂扩展,可以实现拉伸和压缩下的破坏。在工程领域,水力压裂是一项至关重要的技术,尤其在石油和天然气开采等方面应用广泛。而 Comsol 作为强大的多物理场仿真软件,为我们研究多裂纹水力压裂扩展提供了有力工具&a…...
C# .NET 周刊|2026年3月1期
国内文章.NET 11 预览版1:CoreCLR 在 WebAssembly 上的全面集成与性能突破https://www.cnblogs.com/shanyou/p/19629649.NET 11 Preview 1 正式发布,标志着 CoreCLR 运行时能原生支持 WebAssembly。这是微软在跨平台战略上的重大进展。CoreCLR 提供更优性…...
避坑指南:MTK DRM屏兼容中,那些容易让你“点不亮”的硬件与配置细节(附TP复位脚案例)
MTK DRM屏兼容开发实战:从硬件引脚到驱动配置的深度避坑指南 在MTK平台的多屏兼容开发中,工程师们常常会遇到屏幕"点不亮"的棘手问题。这类问题往往源于硬件连接、引脚配置或驱动编译选项中的细微疏忽。本文将结合真实案例,深入剖…...
汇编开发与系统构建:FloppyBird操作系统游戏的技术解构
汇编开发与系统构建:FloppyBird操作系统游戏的技术解构 【免费下载链接】floppybird Floppy Bird (OS) 项目地址: https://gitcode.com/gh_mirrors/fl/floppybird 一、价值:当游戏成为操作系统的技术突破 在计算机科学领域,"操作…...
ParrelSync跨平台终极指南:Windows、macOS和Linux完整配置教程
ParrelSync跨平台终极指南:Windows、macOS和Linux完整配置教程 【免费下载链接】ParrelSync (Unity3D) Test multiplayer without building 项目地址: https://gitcode.com/gh_mirrors/pa/ParrelSync ParrelSync是一款专为Unity3D开发者设计的高效工具&#…...
RAGFlow图片回答避坑指南:为什么不用Base64和阿里云OSS?
RAGFlow图片回答架构设计:从Base64到容器化服务器的技术演进 当RAG系统需要处理包含图片的回答时,技术选型直接关系到系统的性能、安全性和可维护性。本文将深入探讨几种主流方案的优劣对比,并解析为何容器化图片服务器成为当前最优解。 1. 图…...
