Linux 内核通知链和例程代码

概念

大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣。为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子系统,Linux内核提供了通知链的机制。通知链表只能够在内核的子系统之间使用,而不能够在内核与用户空间之间进行事件的通知。通知链表是一个函数链表,链表上的每一个节点都注册了一个函数。当某个事情发生时,链表上所有节点对应的函数就会被执行。所以对于通知链表来说有一个通知方与一个接收方。在通知这个事件时所运行的函数由被通知方决定,实际上也即是被通知方注册了某个函数,在发生某个事件时这些函数就得到执行。其实和系统调用signal的思想差不多。

代码位置

include/linux/notifier.h kernel/notifier.c 代码不超过 1000 行,但是也是因为代码少,才显现出大神的厉害之处。

数据结构

Linux 内核通知链的操刀大神是 Alan Cox,这个大神也是Linux 协议栈头部的大boss,我猜测当时主要是用来做网络方面的通知需要,比如网络IP发生变化需要通知到其他的子系统等等。像类似的usb插拔等也是用到Linux 内核通知链的。

也就是,你想关心我,就提前给我注册回调函数,我发生情况后,我就告诉你,这就好像你小时候出去玩,出去的时候跟你老妈说,妈妈妈妈,煮好饭的时候要记得叫我哈,你妈煮好饭后就在大门口叫,仔啊,快回家吃饭了。

通知链有四种类型:原子通知链( Atomic notifier chains ):通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,不允许阻塞。

对应的链表头结构:

struct atomic_notifier_head	
{    	spinlock_t lock;    	struct notifier_block *head;	
};

可阻塞通知链( Blocking notifier chains ):通知链元素的回调函数在进程上下文中运行,允许阻塞。

对应的链表头:

struct blocking_notifier_head	
{    	struct rw_semaphore rwsem;    	struct notifier_block *head;	
};

原始通知链( Raw notifier chains ):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。

对应的链表头:

struct raw_notifier_head	
{    	struct notifier_block *head;	
};

SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体。

对应的链表头:

struct srcu_notifier_head	
{	struct mutex mutex;	struct srcu_struct srcu;	struct notifier_block *head;	
};

通知链的核心结构:

struct notifier_block	
{	int (*notifier_call)(struct notifier_block *, unsigned long, void *);	struct notifier_block *next;	int priority;	
};

其中notifier_call是通知链要执行的函数指针,next用来连接其它的通知结构,priority是这个通知的优先级,同一条链上的notifier_block{}是按优先级排列的。内核代码中一般把通知链命名为xxx_chain, xxx_nofitier_chain这种形式的变量名。

简单的使用说明

640?wx_fmt=png通知链的事件和注册通知链的回调是对应的,当对应的事件产生了,发送者就触发注册通知链的回调函数执行。

运作机制

通知链的运作机制包括两个角色:

被通知者: 对某一事件感兴趣一方。定义了当事件发生时,相应的处理函数,即回调函数。但需要事先将其注册到通知链中(被通知者注册的动作就是在通知链中增加一项)。•通知者: 事件的通知者。当检测到某事件,或者本身产生事件时,通知所有对该事件感兴趣的一方事件发生。他定义了一个通知链,其中保存了每一个被通知者对事件的处理函数(回调函数)。通知这个过程实际上就是遍历通知链中的每一项,然后调用相应的事件处理函数。

包括以下过程:

•通知者定义通知链。•被通知者向通知链中注册回调函数。•当事件发生时,通知者发出通知(执行通知链中所有元素的回调函数)。

被通知者调用 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;	
}

注销回调函数则使用 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;	
}

通知者调用 notifier_call_chain 函数通知事件的到达,这个函数会遍历通知链中所有的元素,然后依次调用每一个的回调函数(即完成通知动作):

static int __kprobes 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(*nl);	while (nb && nr_to_call)	{	next_nb = rcu_dereference(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表示事件类型,v用来指向通知链上的函数执行时需要用到的参数,一般不同的通知链,参数类型也不一样,例如当通知一个网卡被注册时,v就指向net_device结构,nr_to_call表示准备最多通知几个,-1表示整条链都通知,nr_calls非空的话,返回通知了多少个。

每个被执行的notifier_block回调函数的返回值可能取值为以下几个:

•NOTIFY_DONE:表示对相关的事件类型不关心。•NOTIFY_OK:顺利执行。•NOTIFY_BAD:执行有错。•NOTIFY_STOP:停止执行后面的回调函数。•NOTIFY_STOP_MASK:停止执行的掩码。Notifier_call_chain()把最后一个被调用的回调函数的返回值作为它的返回值。

举例应用

在这里,写了一个简单的通知链表的代码。实际上,整个通知链的编写也就两个过程:

首先是定义自己的通知链的头节点,并将要执行的函数注册到自己的通知链中。其次则是由另外的子系统来通知这个链,让其上面注册的函数运行。这里将第一个过程分成了两步来写,第一步是定义了头节点和一些自定义的注册函数(针对该头节点的),第二步则是使用自定义的注册函数注册了一些通知链节点。分别在代码buildchain.c与regchain.c中。发送通知信息的代码为notify.c。

代码1 buildchain.c。它的作用是自定义一个通知链表test_chain,然后再自定义两个函数分别向这个通知链中加入或删除节点,最后再定义一个函数通知这个test_chain链:

#include <asm/uaccess.h>	
#include <linux/types.h>	
#include <linux/kernel.h>	
#include <linux/sched.h>	
#include <linux/notifier.h>	
#include <linux/init.h>	
#include <linux/types.h>	
#include <linux/module.h>	
MODULE_LICENSE("GPL");	/*	
* 定义自己的通知链头结点以及注册和卸载通知链的外包函数	
*/	/*	
* RAW_NOTIFIER_HEAD是定义一个通知链的头部结点,	
* 通过这个头部结点可以找到这个链中的其它所有的notifier_block	
*/	
static RAW_NOTIFIER_HEAD(test_chain);	/*	
* 自定义的注册函数,将notifier_block节点加到刚刚定义的test_chain这个链表中来	
* raw_notifier_chain_register会调用notifier_chain_register	
*/	
int register_test_notifier(struct notifier_block *nb)	
{	return raw_notifier_chain_register(&test_chain, nb);	
}	
EXPORT_SYMBOL(register_test_notifier);	int unregister_test_notifier(struct notifier_block *nb)	
{	return raw_notifier_chain_unregister(&test_chain, nb);	
}	
EXPORT_SYMBOL(unregister_test_notifier);	/*	
* 自定义的通知链表的函数,即通知test_chain指向的链表中的所有节点执行相应的函数	
*/	
int test_notifier_call_chain(unsigned long val, void *v)	
{	return raw_notifier_call_chain(&test_chain, val, v);	
}	
EXPORT_SYMBOL(test_notifier_call_chain);	/*	
* init and exit	
*/	
static int __init init_notifier(void)	
{	printk("init_notifier\n");	return 0;	
}	static void __exit exit_notifier(void)	
{	printk("exit_notifier\n");	
}	module_init(init_notifier);	
module_exit(exit_notifier);

代码2 regchain.c。该代码的作用是将test_notifier1 test_notifier2 test_notifier3这三个节点加到之前定义的test_chain这个通知链表上,同时每个节点都注册了一个函数:

#include <asm/uaccess.h>	
#include <linux/types.h>	
#include <linux/kernel.h>	
#include <linux/sched.h>	
#include <linux/notifier.h>	
#include <linux/init.h>	
#include <linux/types.h>	
#include <linux/module.h>	
MODULE_LICENSE("GPL");	/*	
* 注册通知链	
*/	
extern int register_test_notifier(struct notifier_block*);	
extern int unregister_test_notifier(struct notifier_block*);	static int test_event1(struct notifier_block *this, unsigned long event, void *ptr)	
{	printk("In Event 1: Event Number is %d\n", event);	return 0;	
}	static int test_event2(struct notifier_block *this, unsigned long event, void *ptr)	
{	printk("In Event 2: Event Number is %d\n", event);	return 0;	
}	static int test_event3(struct notifier_block *this, unsigned long event, void *ptr)	
{	printk("In Event 3: Event Number is %d\n", event);	return 0;	
}	/*	
* 事件1,该节点执行的函数为test_event1	
*/	
static struct notifier_block test_notifier1 =	
{	.notifier_call = test_event1,	
};	/*	
* 事件2,该节点执行的函数为test_event1	
*/	
static struct notifier_block test_notifier2 =	
{	.notifier_call = test_event2,	
};	/*	
* 事件3,该节点执行的函数为test_event1	
*/	
static struct notifier_block test_notifier3 =	
{	.notifier_call = test_event3,	
};	/*	
* 对这些事件进行注册	
*/	
static int __init reg_notifier(void)	
{	int err;	printk("Begin to register:\n");	err = register_test_notifier(&test_notifier1);	if (err)	{	printk("register test_notifier1 error\n");	return -1;	}	printk("register test_notifier1 completed\n");	err = register_test_notifier(&test_notifier2);	if (err)	{	printk("register test_notifier2 error\n");	return -1;	}	printk("register test_notifier2 completed\n");	err = register_test_notifier(&test_notifier3);	if (err)	{	printk("register test_notifier3 error\n");	return -1;	}	printk("register test_notifier3 completed\n");	return err;	
}	/*	
* 卸载刚刚注册了的通知链	
*/	
static void __exit unreg_notifier(void)	
{	printk("Begin to unregister\n");	unregister_test_notifier(&test_notifier1);	unregister_test_notifier(&test_notifier2);	unregister_test_notifier(&test_notifier3);	printk("Unregister finished\n");	
}	module_init(reg_notifier);	
module_exit(unreg_notifier);

代码3 notify.c。该代码的作用就是向test_chain通知链中发送消息,让链中的函数运行:

#include <asm/uaccess.h>	
#include <linux/types.h>	
#include <linux/kernel.h>	
#include <linux/sched.h>	
#include <linux/notifier.h>	
#include <linux/init.h>	
#include <linux/types.h>	
#include <linux/module.h>	
MODULE_LICENSE("GPL");	extern int test_notifier_call_chain(unsigned long val, void *v);	/*	
* 向通知链发送消息以触发注册了的函数	
*/	
static int __init call_notifier(void)	
{	int err;	printk("Begin to notify:\n");	/*	* 调用自定义的函数,向test_chain链发送消息	*/	printk("==============================\n");	err = test_notifier_call_chain(1, NULL);	printk("==============================\n");	if (err)	printk("notifier_call_chain error\n");	return err;	
}	static void __exit uncall_notifier(void)	
{	printk("End notify\n");	
}	module_init(call_notifier);	
module_exit(uncall_notifier);

Makefile文件:

ifneq ($(KERNELRELEASE),)	
EXTRA_CFLAGS = -Wall -g	
obj-m := buildchain.o regchain.o notify.o	
else	
PWD  := $(shell pwd)	
KVER := $(shell uname -r)	
KDIR := /lib/modules/$(KVER)/build	
all:	$(MAKE) -C $(KDIR) M=$(PWD) modules	
clean:	rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions	
endif	

执行结果:640?wx_fmt=png


640?wx_fmt=jpeg

扫码或长按关注

回复「 加群 」进入技术群聊

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

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

相关文章

faster rcnn resnet_RCNN系列、Fast-RCNN、Faster-RCNN、R-FCN检测模型对比

RCNN系列、Fast-RCNN、Faster-RCNN、R-FCN检测模型对比一&#xff0e;RCNN问题一&#xff1a;速度经典的目标检测算法使用滑动窗法依次判断所有可能的区域。本文则预先提取一系列较可能是物体的候选区域&#xff0c;之后仅在这些候选区域上提取特征&#xff0c;进行判断。问题二…

啰嗦一二三

第一点之前有一个抽奖&#xff0c;抽取野火开发板的&#xff0c;我记得有很多人参加了&#xff0c;20号的时候&#xff0c;获奖的同学都主动找我填写了收获地址&#xff0c;但是有一个同学特别调皮&#xff0c;到今天都没有来找我&#xff0c;我等了好久好久&#xff0c;还是没…

go设置后端启动_为什么 Rubyists 应该考虑学习 Go

点击上方蓝色“Go语言中文网”关注我们&#xff0c;领全套Go资料&#xff0c;每天学习 Go 语言如今&#xff0c;越来越少的 Web 开发人员开始专注于 Ruby 之类的单一语言。我们针对不同的工作使用不同的工具。在本文中&#xff0c;Ayooluwa Isaiah 认为 Go 是 Ruby 的完美补充。…

生涯刚开始就要转会?

昨晚&#xff0c;12点快睡觉的时候&#xff0c;一个同学给我发来的问题&#xff0c;篇幅有点长&#xff0c;都是文字&#xff0c;看起来可能有点乏味&#xff0c;不过有耐性的同学还是瞄一下&#xff0c;应该很多人都有这样的迷茫期&#xff0c;我的解答也不一定完全正确&#…

Topaz Video AI 视频修复工具(内附安装压缩包win+Mac)

目录 一、Topaz Video AI 简介 二、Topaz Video AI 安装下载 三、Topaz Video AI 使用 最近玩上了pika1.0和runway的图片转视频&#xff0c;发现生成出来的视频都是有点糊的&#xff0c;然后就找到这款AI修复视频工具 Topaz Video AI。 一、Topaz Video AI 简介 Topaz Video…

python文本编辑器报错_notepad++编辑器中运行python程序时需要注意的编码格式

本篇文章给大家带来的内容是关于notepad编辑器中运行python程序时需要注意的编码格式&#xff0c;有一定的参考价值&#xff0c;有需要的朋友可以参考一下&#xff0c;希望对你有所帮助。 语言&#xff1a;python3.4 文本编辑器&#xff1a;notepad 报错&#xff1a;SyntaxErro…

我不建议大家随便跳槽

突然的留言 我的微信好友很多&#xff0c;经常也有一些同学给我留言一些问题&#xff0c;当然能回答的我肯定会回答了&#xff0c;但是如果是非常难的技术问题&#xff0c;我一般会说&#xff0c;我要请教一下身边的朋友&#xff0c;昨晚准备睡觉的时候&#xff0c;收到一个同…

我是不建议随便跳槽的

突然的留言我的微信好友很多&#xff0c;经常也有一些同学给我留言一些问题&#xff0c;当然能回答的我肯定会回答了&#xff0c;但是如果是非常难的技术问题&#xff0c;我一般会说「我要请教一下身边的朋友」&#xff0c;我会非常刻意的去回避我技术很水的这个问题&#xff0…

ubuntu c++检测usb口事件变化_拆解报告:美式双USBA口充电插座

----- 充电头网拆解报告 第1441篇 -----最近充电头网拿到了一款美式插座&#xff0c;这款产品相比常规墙插插座&#xff0c;除了配有两个双脚AC插口外&#xff0c;还有两个USB-A接口&#xff0c;支持直接插上数据线即可为两台设备进行同时充电。其中两个USB-A口都支持最大5V4.8…

div不继承父类样式_Python编程思想(27):类的继承

-----------支持作者请转发本文-----------李宁老师已经在「极客起源」 微信公众号推出《Python编程思想》电子书&#xff0c;囊括了Python的核心技术&#xff0c;以及Python的主要函数库的使用方法。读者可以在「极客起源」 公众号中输入 160442 开始学习。-----------正文---…

mysql 举例_MySQL 语句举例(一)

举例&#xff1a;有10个用户&#xff0c;输出在订单表中下单数最多的5个人的名字。my_user 表数据my_order&#xff0c;uid对应my_user表的id测试数据生成写一个存储过程&#xff0c;随机插入10000条数据&#xff1a;CREATE DEFINERrootlocalhost PROCEDURE test_loop( )BEGIND…

你知道Linux里D进程会搞事吗?

前言这篇文章是一位大神在实际项目中遇到问题并分析总结出来的&#xff0c;作为新手&#xff0c;能接触到这类文章应该是受益匪浅&#xff0c;这位同学现在在魅族工作&#xff0c;以后也会一直在魅族工作&#xff0c;是Linux 方面的专家&#xff0c;「魅族还有另一个Linux 大神…

react-router 页面离开 提示数据变更

以前项目使用 react-router2.0&#xff0c; 业务层面页面离开的时候需要弹出自己的弹出框&#xff0c;根据用户的操作&#xff0c;进行是否可以离开 试了几种方式都存在问题&#xff0c;实现的并不完美&#xff0c;没办法对用户点击浏览器后退支持的很好&#xff0c;除非是显示…

C语言系列文章之#和##

很久就知道了 # 和 ## &#xff0c;但是都没怎么使用&#xff0c;直到最近的项目涉及到需要编写大量相似的代码之后才决定尝试使用 ## 去简化代码的书写。比如说我的项目需要控制四个通道的电机&#xff0c;四个通道的逻辑控制代码都是类似的&#xff0c;只是对应的硬件和数据信…

springboot 上传文件_基于SpringBoot的文件上传

在实际的企业开发中&#xff0c;文件上传是最常见的功能之一&#xff0c;SpringBoot集成了SpringMVC常用的功能&#xff0c;当然也包含了文件上传的功能&#xff0c;实现起来没有太多的区别。下面我们来讲解一下&#xff0c;使用SpringBoot如何实现多个文件上传操作。使用的环境…

Linux的 i2c 驱动框架分析

1.基本概念总线设备驱动模型&#xff0c;是Linux 内核的一个基础&#xff0c;基本理论可以说按照大企业的分工原则&#xff0c;每个人只要负责自己的事情&#xff0c;向其他部门给出标准的接口调用&#xff0c;后勤部就负责后勤工作&#xff0c;厨房有可能跟后勤部产生工作上的…

matlab fftshift_数字信号处理没有Matlab?用Python一样很爽

通常&#xff0c;在数字信号处理时&#xff0c;我们避不开matlab这个工具&#xff0c;因其它的强大的功能受到广大工程师的好评&#xff0c;也一直都是业界的不二之选。但是&#xff0c;matlab毕竟是商业软件&#xff0c;公司里如果使用的话&#xff0c;就需要支付高昂的费用。…

栈,C语言实现

什么是数据结构&#xff1f;数据结构是什么&#xff1f;要了解数据结构&#xff0c;我们要先明白数据和结构&#xff0c;数据就是一些int char 这样的变量&#xff0c;这些就是数据&#xff0c;如果你是一个篮球爱好者&#xff0c;那么你的球鞋就是你的数据&#xff0c;结构就是…

Camera摄像头工作原理

回想这工作的这几年&#xff0c;尝尽社会的辛酸艰难&#xff0c;从一开始什么都没有到30万&#xff0c;从30万到200万&#xff0c;从200万到1300万&#xff0c;不是炫耀&#xff0c;我只是想通过我自己的经历告诉我的朋友们「手机像素越高&#xff0c;拍的照片越清晰」摄像头结…

es6一维数组转二维数组_技术图文:Numpy 一维数组 VS. Pandas Series

背景Numpy 提供的最重要的数据结构是 ndarray&#xff0c;它是 Python 中 list 的扩展。Pandas 提供了两种非常重要的数据结构 Series和DataFrame。Numpy 中的一维数组与 Series 相似&#xff0c;一维数组只是提供了从0开始与位置有关的索引&#xff0c;而Series除了位置索引之…