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

news2024/11/25 6:59:07

文章目录

    • 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_NOTIFIERS
		if (unlikely(!func_ptr_is_kernel_text(nb->notifier_call))) {
			WARN(1, "Invalid notifier called!");
			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--;
	}
	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打印出来。输出结果如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/722994.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从源码全面解析 Java SPI 的来龙去脉

一、引言 对于 Java 开发者而言&#xff0c;关于 dubbo &#xff0c;我们一般当做黑盒来进行使用&#xff0c;不需要去打开这个黑盒。 但随着目前程序员行业的发展&#xff0c;我们有必要打开这个黑盒&#xff0c;去探索其中的奥妙。 虽然现在是互联网寒冬&#xff0c;但乾坤…

数字化转型应该从哪里开始?

数字化转型可能是一个复杂的过程&#xff0c;涉及将数字技术和战略集成到组织的各个方面。虽然具体的起点可能会根据组织的规模、行业和目标而有所不同&#xff0c;但数字化转型计划通常从以下几个共同领域开始&#xff1a; 愿景和战略&#xff1a;转型之旅应从与组织目标一致的…

百万数据SQL优化技巧,看这一篇就够了(实操+详细总结)

前言&#xff1a;这次准备了100W的数据进行SQL性能测试&#xff0c;数据库采用的是MySQL&#xff0c;总共介绍了常见的15种SQL优化方式&#xff0c;每一种优化方式都进行了实打实的测试&#xff0c;逐行讲解&#xff0c;通俗易懂&#xff01; 目录 一、准备数据 1、创建表结构…

Windows环境下安装和配置python环境

Windows环境下安装和配置python环境 1.官网下载&#xff1a;https://www.python.org/downloads/release 2.安装&#xff1a;自定义路径即可无脑下一步 3.cmd打开控制台&#xff0c;输入python&#xff0c;如果页面切换成以下样子就说明安装成功了 4.运行python脚本【步骤3切入…

555、Vue 3 学习笔记 -【常用Composition API(四)】 2023.07.06

目录 一、setup的两个注意点1. setup执行的时机2. setup的参数 二、 计算属性与监视1. computed函数2. watch函数3. watchEffect函数 三、参考链接 一、setup的两个注意点 1. setup执行的时机 在beforeCreate之前执行一次&#xff0c;this是undefined 2. setup的参数 props…

GitLab名词介绍

GitLab名词介绍 分支&#xff1a;active、stale、default、protected IDEA中git面板&#xff1a;本地、远程、HEAD 合并时的选项&#xff1a;Delete、squash 查看Git常用操作 分支&#xff1a;active、stale、default、protected 在分支页面下&#xff0c;有active、stale…

k8s中kubectl陈述式/声明式资源管理

k8s陈述资源管理方法的说明 1.kubernetes 集群管理集群资源的唯一入口是通过相应的方法调用 apiserver 的接口 2.kubectl 是官方的CLI命令行工具&#xff0c;用于与 apiserver 进行通信&#xff0c;将用户在命令行输入的命令&#xff0c;组织并转化为 apiserver 能识别的信息&…

TongWeb8关于内存泄露提示: To prevent a memory leak

原因&#xff1a; 该问题与 https://blog.csdn.net/realwangpu/article/details/109510297 相同&#xff0c;TongWeb7, TongWeb8在卸载应用时&#xff0c;会尝试回收可能存在的内存泄露&#xff0c; 本质应该从应用方面解决。 解决办法&#xff1a; 若无法修改应用&#xff0c…

深度学习神经网络学习笔记-论文研读-transformer

摘要 优势序列转导模型基于复杂的循环或包括一个编码器和一个解码器的卷积神经网络。最好的表现良好的模型还通过attention 连接编码器和解码器机制。我们提出了一种新的简单的网络架构&#xff0c;Transformer&#xff0c; 完全基于注意力机制&#xff0c;省去了递归和卷积完…

【成都理工826】22年真题及解析

哈喽大家好&#xff0c;鉴于真题系列反馈很不错&#xff0c;我决定重启真题系列&#xff01;之前更新的22真题合集点击这里&#xff1a; 成都理工826信号与系统难度不是特别大&#xff0c;但是对计算的要求比较高。掌握好基础&#xff0c;计算细心是可以拿高分的。本套试题内容…

【我们一起60天准备考研算法面试(大全)-第四天 4/60(二叉搜索树与表达式树)】【每天40分钟,我们一起用60天准备 考研408-数据结构(笔试)】

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…

github搜索技巧笔记

一、了解 GitHub Watch按钮 Watch可以理解为关注的意思&#xff0c;默认情况下是Not watching&#xff0c;当选择Watch后&#xff0c;你会收到这个GitHub项目的所有动态。比如&#xff1a;有人发起pull request或者issue等。接收动态方式包括个人通知中心或者邮箱。 如果某个…

TypeScript - 函数(中)

目录 1、编写良好泛型函数的准则 1.1 向下推送类型参数 1.2 使用较少的类型参数 1.3 类型参数应出现两次 2、可选参数 3、回调中的可选参数 4、函数重载 5、重载签名和实现签名 6、写好重载 1、编写良好泛型函数的准则 编写泛型函数很有趣&#xff0c;并且很容易被类…

Kibana对索引库的操作(3)

这里我们主要是对索引库进行各种查询的操作,所以要提前准备一些数据 POST /leq/goods/3 {"title": "小米手机","images": "3.jpg","price": 4299,"stock": 200,"saleable": true,"subTitle":…

读发布!设计与部署稳定的分布式系统(第2版)笔记20_实例层之代码

1. 术语的定义 1.1. 服务 1.1.1. 指共同协作、以单元的形式对外提供功能的跨机器进程集合 1.1.2. 一个服务可以由多种可执行文件组成 1.1.3. 一个服务可能包含来自多个可执行文件的多个进程 1.1.4. 可能对外呈现单个IP地址&#xff0c;并在后台进行负载均衡 1.1.5. 可能有…

潇洒郎: bat文件与vbs文件, 执行命令行命令以及执行时无console, reg文件注册表

截图.bat start snippingtool 锁屏.bat rundll32.exe user32.dll,LockWorkStation redis.bat cd %~dp0 redis-server.exe redis.windows.conf vbs Set ws CreateObject("Wscript.Shell") ws.run "cmd /c C:/Python27/Scripts/ride.bat",vbhide 在上…

记录好项目D23

记录好项目 你好呀&#xff0c;这里是我专门记录一下从某些地方收集起来的项目&#xff0c;对项目修改&#xff0c;进行添砖加瓦&#xff0c;变成自己的闪亮项目。修修补补也可以成为毕设哦 本次的项目是个基于Springboot教务管理系统 一、系统介绍 这个项目是一个简单的教…

Slowhttptest----DoS攻击工具

Slowhttptest----DoS攻击工具 文章目录 slowhttptestcentos下安装slowhttptestUbuntu 20.04 LTS下安装slowhttptest使用 slowhttptest SlowHTTPTest 是依赖 HTTP 协议的慢速攻击 DoS 攻击工具。 工具的设计基本原理是服务器在请求完全接收后才会进行处理。 如果客户端的 HTT…

香港银行开个人账户应该选择哪个银行,附详细指南

香港现在陆续通关&#xff0c;开户是越来越方便&#xff0c;目前已经很多有账户需求的都过去开户了&#xff0c;那现在个人在香港银行开户的话可以开哪些银行呢&#xff1f;我列举了几个&#xff0c;可以参考下 汇丰个人户&#xff0c;当场开好拿卡使用 恒生个人户&#xff0…

商标注册 | 美国商标注册资料、流程、时间、方式一文详解

中国对外贸易市场中&#xff0c;美国是当仁不让的重要出口市场&#xff0c;这成为众多国内企业产品出海的先选目的地。产品出口也会涉及到一系列知识产权问题&#xff0c;而这个问题之一就是美国商标的注册。 美国的商标保护非常完善&#xff0c;它不仅可以阻止侵权商品进入美…