当前位置: 首页 > news >正文

【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(&notifier_two);register_notifier(&notifier_one);return 0;
}static void __exit module_1_exit(void)
{unregister_notifier(&notifier_two);unregister_notifier(&notifier_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发送两个参数:actiondata,并通过module_1打印出来。输出结果如下:

相关文章:

【linux kernel】一文总结linux内核通知链

文章目录 1、通知链简介2、通知链的类型3、原理分析和API&#xff08;1&#xff09;注销通知器&#xff08;2&#xff09;注销通知器&#xff08;3&#xff09;通知链的通知 4、实例代码&#xff08;1&#xff09;定义一个通知链&#xff08;2&#xff09;实现观察者模块&#…...

kafka入门,Kafka 副本(十三)

Kafka副本 副本基本信息 1&#xff09;Kafka副本作用&#xff0c;提高数据可靠性 2&#xff09;Kafka默认副本1个&#xff0c;生产环境一般配置2个&#xff0c;保证数据可靠性&#xff0c;太多副本会增加磁盘存储空间&#xff0c;增加网络上数据传输&#xff0c;降低效率 3&a…...

利用PPT制作简单的矢量图

1.用PPT画一个图形&#xff08;可以多个图&#xff09; 2.鼠标圈住图形 3.利用 Ctrl G 组合图形&#xff0c;再用 Ctrl C 复制 4.打开word—粘贴—选择性粘贴—图片&#xff08;增强性图元文件&#xff09; 确认即可。...

18-Linux 常用命令

目录 1.ls PS&#xff1a;FinalShell设置背景和字体 2.pwd 3.cd PS&#xff1a;认识 Linux 目录结构——Linux 是一个树形目录结构 PS&#xff1a;绝对路径 vs 相对路径 PS&#xff1a;使用 tab 键补全 PS&#xff1a;使用 ctrl c 重新输入 4.touch PS&#xff1a;L…...

2024考研408-计算机组成原理第六章-总线学习笔记

文章目录 前言初识总线一、总线概述1.1、总线的概述1.1.1、认识总线1.1.2、设计总线需要的特性1.1.3、总线的分类①按照数据传输格式分&#xff08;串行、并行&#xff09;②按照总线功能连接的总线&#xff08;片内总线、系统总线、通信总线&#xff09;③按照时序控制方式&am…...

uni_app 微信小程序 苹果手机 边框显示不全

![在这里插入图片描述](https://img-blog.csdnimg.cn/3a4c4ab1a146444c84c72d360a057c01.png 解决方案&#xff1a; 原因&#xff1a;是因为我们在设置边框的时候设置的rpx &#xff0c;自适应会自动换算px, 两者之间的比例一般都是1.5-2之间&#xff0c;对于边框 border 来说…...

vue 访问第三方 跨域, 配置vue.config.js

目录 0 config 文件被修改 一个要重启vscode 配置文件才会生效 1 第一种 (有两种写法) 1.1 配置vue.config.js 1.2 axios 使用 1.3 终端打印 2 第二种方法 --> 错误 --> 没有运行成功 2.1 配置vue.config.js --> 就是api 不被设置成 替换为 / 2.2 axios 使用…...

使用gradio库的File模块实现文件上传和展示

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…...

网络安全进阶学习第四课——SSRF服务器请求伪造

文章目录 一、什么是SSRF&#xff1f;二、SSRF成因三、SSRF简析四、PHP存在SSRF的风险函数五、后台源码获取方式六、SSRF危害七、SSRF漏洞挖掘从WEB功能上寻找&#xff0c;从URL关键字中寻找 八、SSRF具体利用ssrf常利用的相关协议PHP伪协议读取文件端口扫描 九、SSRF存在的必要…...

js处理扁平数组和树结构相互转换

一、将扁平的数据转为树形结构 在 js中&#xff0c;可以使用递归算法将扁平的数据转换为树形结构。 扁平数据通常是一个带有 parentId 属性的数组&#xff0c;而树形结构通常是一个带有 children 属性的对象。 1、方法一 下面是一个简单的例子&#xff0c;演示如何将扁平数…...

Spark弹性分布式数据集

1. Spark RDD是什么 RDD&#xff08;Resilient Distributed Dataset&#xff0c;弹性分布式数据集&#xff09;是一个不可变的分布式对象集合&#xff0c;是Spark中最基本的数据抽象。在代码中RDD是一个抽象类&#xff0c;代表一个弹性的、不可变、可分区、里面的元素可并行计…...

ffmpeg学习记录

1、对图片进行裁剪 ffmpeg -i input.jpg -vf cropiw/3:ih:20:0 caijian.jpg PS&#xff1a; crop100:100:12:34 相同效果: cropw100:h100:x12:y34 2、视频增加文字水印 使用drawtext滤镜进行增加水印 参数 类型 说明 text 字符串 文字 textfile 字符串 文字文件 …...

ChatGPT:为教育创新提供五大机遇

随着智能技术的不断发展&#xff0c;ChatGPT在教育场景中的创新价值可能比我们能够意识到的还要多。比如它可以自动处理作业、在线答疑&#xff0c;可以辅助语言学习、实时沟通&#xff0c;甚至还可以用于评估诊断、科学研究。国内外关于利用ChatGPT实现教育创新的场景描绘已经…...

Educational Codeforces Round 151 (Rated for Div. 2)

Edu 151 A. Forbidden Integer 题意&#xff1a; 你有[1, k]内除了 x x x的整数&#xff0c;每个数可以拿多次&#xff0c;问 ∑ n \sum n ∑n是否可行并构造 思路&#xff1a; 有1必能构造&#xff0c;否则假如没有1&#xff0c;假如有2, 3必定能构造出大于等于2的所有数&…...

【AI机器学习入门与实战】机器学习算法都有哪些分类?

&#x1f44d;【AI机器学习入门与实战】目录 &#x1f36d;基础篇 &#x1f525; 第一篇&#xff1a;【AI机器学习入门与实战】AI 人工智能介绍 &#x1f525; 第二篇&#xff1a;【AI机器学习入门与实战】机器学习核心概念理解 &#x1f525; 第三篇&#xff1a;【AI机器学习入…...

React之hooks

Hooks函数 1.useState()&#xff1a;状态钩子。纯函数组件没有状态&#xff0c;用于为函数组件引入state状态, 并进行状态数据的读写操作。 const [state, setState] useState(initialValue); // state&#xff1a;初始的状态属性&#xff0c;指向状态当前值&#xff0c;类似…...

1.监控分布式--zabbix

文章目录 监控分布式-zabbix、prometheus概念工作原理功能组件部署zabbix安装Nginx和PHP环境部署数据库编码安装zabbix编译安装zabbix server客户端安装zabbix agent服务 监控分布式-zabbix、prometheus 利用一个优秀的监控软件&#xff0c;我们可以: 通过一个友好的界面进行…...

java stream 多个集合去重取交集

文章目录 背景案例代码 背景 原因是需要从表里查多个集合list&#xff0c;然后取多个集合得交集&#xff0c;并且元素是对象&#xff0c;所以使用了下面的方式&#xff0c;当然方式有很多种&#xff0c;仅供参考。 案例 下面提供了一段多个集合join取交集的例子&#xff0c;…...

给LLM装上知识:从LangChain+LLM的本地知识库问答到LLM与知识图谱的结合

第一部分 什么是LangChain&#xff1a;连接本地知识库与LLM的桥梁 作为一个 LLM 应用框架&#xff0c;LangChain 支持调用多种不同模型&#xff0c;提供相对统一、便捷的操作接口&#xff0c;让模型即插即用&#xff0c;这是其GitHub地址&#xff0c;其架构如下图所示 (点此查…...

视频与AI,与进程交互(二) pytorch 极简训练自己的数据集并识别

目标学习任务 检测出已经分割出的图像的分类 2 使用pytorch pytorch 非常简单就可以做到训练和加载 2.1 准备数据 如上图所示&#xff0c;用来训练的文件放在了train中&#xff0c;验证的文件放在val中&#xff0c;train.txt 和 val.txt 分别放文件名称和分类类别&#xff…...

LLM - 第2版 ChatGLM2-6B (General Language Model) 的工程配置

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/131445696 ChatGLM2-6B 是开源中英双语对话模型 ChatGLM-6B 的第二代版本&#xff0c;在保留了初代模型对话流畅、部署门槛较低等众多优…...

从0开始,手写MySQL事务

说在前面&#xff1a;从0开始&#xff0c;手写MySQL的学习价值 尼恩曾经指导过的一个7年经验小伙&#xff0c;凭借精通Mysql, 搞定月薪40K。 从0开始&#xff0c;手写一个MySQL的学习价值在于&#xff1a; 可以深入地理解MySQL的内部机制和原理&#xff0c;Mysql可谓是面试的…...

React中useState的setState方法请求了好多次

1、问题描述 最近在写react的时候碰到了一个很奇怪的问题。 可以看到那个getXXX()的方法一直不断的被调用&#xff0c;网页一直请求&#xff0c;根本停不下来了。 2、产生原因 要弄明白这个原因&#xff0c;首先要先了解一下react生命周期。 react是组件式的编程&#xff0c;一…...

【MYSQL基础】基础命令介绍

基础命令 MYSQL注释方式 -- 单行注释/* 多行注释 哈哈哈哈哈 哈哈哈哈 */连接数据库 mysql -u root -p12345678退出数据库连接 使用exit;命令可以退出连接 查询MYSQL版本 mysql> select version(); ----------- | version() | ----------- | 8.0.27 | ----------- 1…...

多元回归预测 | Matlab基于灰狼算法优化深度置信网络(GWO-DBN)的数据回归预测,matlab代码回归预测,多变量输入模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元回归预测 | Matlab基于灰狼算法优化深度置信网络(GWO-DBN)的数据回归预测,matlab代码回归预测,多变量输入模型,matlab代码回归预测,多变量输入模型,多变量输入模型 评价指标包括:MAE、RMSE和R2等,代码质…...

校园wifi网页认证登录入口

很多校园wifi网页认证登录入口是1.1.1.1 连上校园网在浏览器写上http://1.1.1.1就进入了校园网 使 用 说 明 一、帐户余额 < 0.00元时&#xff0c;帐号被禁用&#xff0c;需追加网费。 二、在计算中心机房上机的用户&#xff0c;登录时请选择新建帐号时给您指定的NT域&…...

[SpringBoot]Spring Security框架

目录 关于Spring Security框架 Spring Security框架的依赖项 Spring Security框架的典型特征 关于Spring Security的配置 关于默认的登录页 关于请求的授权访问&#xff08;访问控制&#xff09; 使用自定义的账号登录 使用数据库中的账号登录 关于密码编码器 使用BCry…...

Unity 之 抖音小游戏本地数据最新存储方法分享

Unity 之 抖音小游戏本地数据最新存储方法分享 一、抖音小游戏文件存储系统背景二、文件存储系统的使用方法2.1 初始化2.1 创建目录2.3 存储数据2.4 删除目录/文件2.5 其他相关操作 三&#xff0c;小结 抖音小游戏是一种基于抖音平台开发的小型游戏&#xff0c;与传统的 APP 不…...

逍遥自在学C语言 | 函数初级到高级解析

前言 函数是C语言中的基本构建块之一&#xff0c;它允许我们将代码组织成可重用、模块化的单元。 本文将逐步介绍C语言函数的基础概念、参数传递、返回值、递归以及内联函数和匿名函数。 一、人物简介 第一位闪亮登场&#xff0c;有请今后会一直教我们C语言的老师 —— 自在…...

Elastic 推出 Elastic AI 助手

作者&#xff1a;Mike Nichols Elastic 推出了 Elastic AI Assistant&#xff0c;这是一款由 ESRE 提供支持的开放式、生成式 AI 助手&#xff0c;旨在使网络安全民主化并支持各种技能水平的用户。 最近发布的 Elasticsearch Relevance Engine™ (ESRE™) 提供了用于创建高度相…...