您当前的位置:首页 > 电脑百科 > 软件技术 > 操作系统 > linux百科

一文搞懂Linux内核通知链(Notifier)

时间:2022-07-14 16:06:16  来源:  作者:原天堂没有代码

引入

linux内核中,各个子系统之间有很强的相互关系,某些子系统可能对其他子系统产生的事件比较感兴趣。因此内核引入了notifier机制,当然了notifier机制只能用在内核子系统之间,不能用在内核与应用层之间。比如当系统suspend的时候,就会使用到notifier机制来通知系统的内核线程进行suspend。

嵌入式进阶教程分门别类整理好了,看的时候十分方便,由于内容较多,这里就截取一部分图吧。

一文搞懂Linux内核通知链(Notifier)

 

需要的朋友私信【内核】即可领取。

内核学习地址:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈-学习视频教程-腾讯课堂

内核实现的notifier机制代码位于kernel/kernel/notifier.c,同时此机制的代码量也不是很多只有600行左右。

数据结构

内核使用struct notifier_block结构代表一个notifier

typedef	int (*notifier_fn_t)(struct notifier_block *nb,
			unsigned long action, void *data);

struct notifier_block {
	notifier_fn_t notifier_call;
	struct notifier_block __rcu *next;
	int priority;
};

notifier_call: 代表当事件发生之后调用的回调函数。

next: 用来链接同一个类型的notifier。

priority: notifier chAIn的优先级。对应的数字越大优先级越高,就优先执行。

同时内核也提供了四种不同类型的notifier chain

  • 原子通知链(Atomic notifier chains)
struct atomic_notifier_head {
	spinlock_t lock;
	struct notifier_block __rcu *head;
};

可以看到原子notifier chain只是对notifier_block的一个封装。同时atomic notifier chain的回调函数需要运行在中断上下文/原子上下文中,而且不能睡眠。

很明显因为atomic_notifer_head其中的spin_lock的特点就是不能睡眠。

  • 可阻塞通知链(Blocking notifier chains)
struct blocking_notifier_head {
	struct rw_semaphore rwsem;
	struct notifier_block __rcu *head;
};

blocking_notifier_head其中包含了读写信号量成员rwsem,而信号量的特定就是运行在进程上下文,而且还可以睡眠。同理Blocking notifier chains的回调函数特征一样。

  • 原始通知链(Raw notifier chains)
struct raw_notifier_head {
	struct notifier_block __rcu *head;
};

raw_notifier_head的特点是对回调函数,register, unregister都没有任何限制,所有的保护机制都需要调用者维护。

  • SRCU通知链(SRCU notifier chains)
struct srcu_notifier_head {
	struct mutex mutex;
	struct srcu_struct srcu;
	struct notifier_block __rcu *head;
};

SRCU通知链是block notifier chain的一种变体,采用SRCU(Sleepable Read-Copy Update)代替rw-semphore来保护chains

notifier chain初始化

内核提供了一套宏用来初始化各个类型的通知链

#define ATOMIC_INIT_NOTIFIER_HEAD(name) do {	
		spin_lock_init(&(name)->lock);	
		(name)->head = NULL;		
	} while (0)
#define BLOCKING_INIT_NOTIFIER_HEAD(name) do {	
		init_rwsem(&(name)->rwsem);	
		(name)->head = NULL;		
	} while (0)
#define RAW_INIT_NOTIFIER_HEAD(name) do {	
		(name)->head = NULL;		
	} while (0)

以上是动态初始化各个类型的通知链,当然了有动态初始化,也就有静态初始化

#define ATOMIC_NOTIFIER_INIT(name) {				
		.lock = __SPIN_LOCK_UNLOCKED(name.lock),	
		.head = NULL }
#define BLOCKING_NOTIFIER_INIT(name) {				
		.rwsem = __RWSEM_INITIALIZER((name).rwsem),	
		.head = NULL }
#define RAW_NOTIFIER_INIT(name)	{				
		.head = NULL }
/* srcu_notifier_heads cannot be initialized statically */

#define ATOMIC_NOTIFIER_HEAD(name)				
	struct atomic_notifier_head name =			
		ATOMIC_NOTIFIER_INIT(name)
#define BLOCKING_NOTIFIER_HEAD(name)				
	struct blocking_notifier_head name =			
		BLOCKING_NOTIFIER_INIT(name)
#define RAW_NOTIFIER_HEAD(name)					
	struct raw_notifier_head name =				
		RAW_NOTIFIER_INIT(name)

通过注释可以知道SRCU通知链不能使用静态的方法,因此内核提供了一个动态的初始化函数,

void srcu_init_notifier_head(struct srcu_notifier_head *nh)
{
	mutex_init(&nh->mutex);
	if (init_srcu_struct(&nh->srcu) < 0)
		BUG();
	nh->head = NULL;
}

注册/注销通知链

内核提供的最基本的注册通知链的函数

/*
 *	Notifier chain core routines.  The exported routines below
 *	are layered on top of these, with Appropriate locking added.
 */

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;
}

上述的操作就是通过判断priority的大小,然后将大的插入带链表头,小的插入在链表末尾。

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;
}

上述的注销函数,就是先找到此节点,然后从链表中删除的一个操作。

因为插入/删除操作都是临界资源,需要使用rcu机制保护起来。

同样,内核通过包装核心的注册/注销函数,实现了上述说的四种notifier chain

int atomic_notifier_chain_register(struct atomic_notifier_head *nh,struct notifier_block *n)
int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh,struct notifier_block *n)

int blocking_notifier_chain_register(struct blocking_notifier_head *nh,struct notifier_block *n)
int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh,struct notifier_block *n)

int raw_notifier_chain_register(struct raw_notifier_head *nh,struct notifier_block *n)
int raw_notifier_chain_unregister(struct raw_notifier_head *nh,struct notifier_block *n)

int srcu_notifier_chain_register(struct srcu_notifier_head *nh,struct notifier_block *n).
int srcu_notifier_chain_unregister(struct srcu_notifier_head *nh,struct notifier_block *n)

通知函数

当某种事件需要发生的时候,就需要调用内核提供的通知函数notifier call函数,来通知注册过相应时间的子系统。

/**
 * notifier_call_chain - Informs the registered notifiers about an event.
 *	@nl:		Pointer to head of the blocking notifier chain
 *	@val:		Value passed unmodified to notifier function
 *	@v:		Pointer passed unmodified to notifier function
 *	@nr_to_call:	Number of notifier functions to be called. Don't care
 *			value of this parameter is -1.
 *	@nr_calls:	Records the number of notifications sent. Don't care
 *			value of this field is NULL.
 *	@returns:	notifier_call_chain returns the value returned by the
 *			last notifier function called.
 */
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);
		ret = nb->notifier_call(nb, val, v);          //调用注册的回调函数

		if (nr_calls)
			(*nr_calls)++;

		if ((ret & NOTIFY_STOP_MASK) == NOTIFY_STOP_MASK)  //有停止的mask就返回,否则继续
			break;
		nb = next_nb;
		nr_to_call--;
	}
	return ret;
}

同样内核也提供了四个不同类型的通知函数

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)
int srcu_notifier_call_chain(struct srcu_notifier_head *nh,unsigned long val, void *v)

示例分析

通过编写两个文件,一个用来注册事件,另一个用来通知事件。

notifier.c用来注册事件

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/notifier.h>


BLOCKING_NOTIFIER_HEAD(test_chain_head);
EXPORT_SYMBOL_GPL(test_chain_head);

int register_test_notifier(struct notifier_block *nb)
{
	return blocking_notifier_chain_register(&test_chain_head, nb);
}

int unregister_test_notifier(struct  notifier_block *nb)
{
	return blocking_notifier_chain_unregister(&test_chain_head, nb);
}

static int test_chain_notify(struct notifier_block *nb,unsigned long mode, void *_unused)
{
	printk(KERN_EMERG "notifier: test_chain_notify!n");        //回调处理函数
	return 0;
}

static struct notifier_block test_chain_nb = {
	.notifier_call = test_chain_notify,
};


static int notifier_test_init(void)
{
	printk(KERN_EMERG "notifier: notifier_test_init!n");        
	register_test_notifier(&test_chain_nb);                       //注册notifier事件

	return 0;
}

static void notifier_test_exit(void)
{
	printk(KERN_EMERG "notifier: notifier_test_exit!n");
	unregister_test_notifier(&test_chain_nb);
}

module_init(notifier_test_init);
module_exit(notifier_test_exit);
MODULE_LICENSE("GPL v2");

call.c用来触发事件。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/notifier.h>

extern struct blocking_notifier_head test_chain_head;

static int call_notifier_call_chain(unsigned long val)
{
	int ret = blocking_notifier_call_chain(&test_chain_head, val, NULL);
	return notifier_to_errno(ret);
}

static int call_test_init(void)
{
	printk(KERN_EMERG "notifier: call_test_init!n");
	call_notifier_call_chain(123); //在init函数中触发事件

	return 0;
}

static void call_test_exit(void)
{
	printk(KERN_EMERG "notifier: call_test_exit!n");
}
module_init(call_test_init);
module_exit(call_test_exit);
MODULE_LICENSE("GPL v2");

测试结构如下:

root@test:/data # insmod notifier.ko                                         
root@test:/data # insmod call.ko                                    
root@test:/data # dmesg | grep "notifier"                           
[   89.644596] c7 notifier: notifier_test_init!
[   95.956801] c6 notifier: call_test_init!
[   95.960624] c6 notifier: test_chain_notify!


Tags:Notifier   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
一文搞懂Linux内核通知链(Notifier)
引入在linux内核中,各个子系统之间有很强的相互关系,某些子系统可能对其他子系统产生的事件比较感兴趣。因此内核引入了notifier机制,当然了notifier机制只能用在内核子系统之...【详细内容】
2022-07-14  Search: Notifier  点击:(842)  评论:(0)  加入收藏
▌简易百科推荐
微软 Win11 Linux 子系统(WSL)发布 2.2.2 版本
IT之家 4 月 8 日消息,微软近日更新 Windows Subsystem for Linux(WSL),最新 2.2.2 版本中带来了诸多改进,重点更新了 nft 规则,可以让 IPv6 流量通过 Linux 容器。图源: dev.to,AI...【详细内容】
2024-04-08    IT之家  Tags:Linux   点击:(10)  评论:(0)  加入收藏
从原理到实践:深入探索Linux安全机制
Linux 是一种开源的类Unix操作系统内核,由Linus Torvalds在1991年首次发布,其后又衍生出许多不同的发行版(如Ubuntu、Debian、CentOS等)。前言本文将从用户和权限管理、文件系统...【详细内容】
2024-03-27  凡夫编程  微信公众号  Tags:Linux安全   点击:(26)  评论:(0)  加入收藏
在Linux系统中,如何处理内存管理和优化的问题?
本文对 Linux 内存管理和优化的一些高级技巧的详细介绍,通过高级的内存管理技巧,可以帮助系统管理员和开发人员更好地优化 Linux 系统的内存使用情况,提高系统性能和稳定性。在...【详细内容】
2024-03-26  编程技术汇  微信公众号  Tags:Linux   点击:(18)  评论:(0)  加入收藏
Linux 6.9-rc1 内核发布:AMD P-State 首选核心、BH 工作队列
IT之家 3 月 25 日消息,Linus Torvalds 宣布,Linux 6.9 内核的首个 RC(候选发布)版 Linux 6.9-rc1 发布。▲ Linux 6.9-rc1Linus 表示,Linux 内核 6.9 看起来是一个“相当正常”...【详细内容】
2024-03-25    IT之家  Tags:Linux   点击:(16)  评论:(0)  加入收藏
轻松实现Centos系统的软件包安装管理:yum指令实战详解
yum 是一种用于在 CentOS、Red Hat Enterprise Linux (RHEL) 等基于 RPM 的 Linux 发行版上安装、更新和管理软件包的命令行工具。它可以自动解决软件包依赖关系,自动下载并...【详细内容】
2024-02-27  凡夫贬夫  微信公众号  Tags:Centos   点击:(61)  评论:(0)  加入收藏
Win + Ubuntu 缝合怪:第三方开发者推出“Wubuntu”Linux 发行版
IT之家 2 月 26 日消息,一位第三方开发者推出了一款名为“Wubuntu”的缝合怪 Linux 发行版,系统本身基于 Ubuntu,但界面为微软 Windows 11 风格,甚至存在微软 Windows 徽标。据...【详细内容】
2024-02-27    IT之家  Tags:Ubuntu   点击:(55)  评论:(0)  加入收藏
Linux中磁盘和文件系统工作原理解析
在Linux系统中,一切皆文件的概念意味着所有的资源,包括普通文件、目录以及设备文件等,都以文件的形式存在。这种统一的文件系统管理方式使得Linux系统具有高度的灵活性和可扩展...【详细内容】
2024-02-20  王建立    Tags:Linux   点击:(61)  评论:(0)  加入收藏
Linux子系统概览
inux操作系统是一个模块化的系统,由多个子系统组成。这些子系统协同工作,使Linux能够执行各种任务。了解Linux的子系统有助于更好地理解整个操作系统的运作机制。以下是Linux...【详细内容】
2024-02-01    简易百科  Tags:Linux   点击:(89)  评论:(0)  加入收藏
Linux内核:系统之魂与交互之源
内核,作为任何基于Linux的操作系统的心脏,扮演着至关重要的角色。它不仅是计算机系统软件与硬件之间的桥梁,更是确保系统稳定、高效运行的关键。内核提供了一系列核心功能,为上...【详细内容】
2024-02-01  松鼠宝贝    Tags:Linux内核   点击:(74)  评论:(0)  加入收藏
如何确保Linux进程稳定与持久
在Linux系统中,进程的稳定性与持久性对于维持系统的持续运行至关重要。然而,由于各种原因,进程可能会面临崩溃或系统重启的情况。为了确保关键进程能够持续运行,我们必须采取一...【详细内容】
2024-01-19  松鼠宝贝    Tags:Linux进程   点击:(94)  评论:(0)  加入收藏
相关文章
    无相关信息
站内最新
站内热门
站内头条