Linux内核基础--事件通知链(notifier chain)good【转】
转自:http://www.cnblogs.com/pengdonglin137/p/4075148.html
阅读目录(Content)
- 1.1. 概述
- 1.2.数据结构
- 1.3. 运行机理
- 1.4. 简单一例:
- 一、概述
- 二、结构体
- 三、操作过程
- 四、实例
转载:
http://blog.csdn.net/wuhzossibility/article/details/8079025
http://blog.chinaunix.net/uid-27717694-id-4286337.html
内核通知链
1.1. 概述
Linux内核中各个子系统相互依赖,当其中某个子系统状态发生改变时,就必须使用一定的机制告知使用其服务的其他子系统,以便其他子系统采取相应的措施。为满足这样的需求,内核实现了事件通知链机制(notificationchain)。
通知链只能用在各个子系统之间,而不能在内核和用户空间进行事件的通知。组成内核的核心系统代码均位于kernel目录下,通知链表位于 kernel/notifier.c中,对应的头文件为include/linux/notifier.h。通知链表机制并不复杂,实现它的代码只有区区 几百行。
事件通知链表是一个事件处理函数的列表,每个通知链都与某个或某些事件有关,当特定的事件发生时,就调用相应的事件通知链中的回调函数,进行相应的处理。
1.2.数据结构
如图 1中所示,Linux的网络子系统一共有3个通知链:表示ipv4地址发生变化时的inetaddr_chain;表示ipv6地址发生变化的inet6addr_chain;还有表示设备注册、状态变化的netdev_chain。
在这些链中都是一个个notifier_block结构:
struct notifier_block {int (*notifier_call)(struct notifier_block *, unsigned long, void *);struct notifier_block *next;int priority; };
其中,
1. notifier_call:当相应事件发生时应该调用的函数,由被通知方提供,如other_subsys_1;
2. notifier_block *next:用于链接成链表的指针;
3. priority:回调函数的优先级,一般默认为0。
内核代码中一般把通知链命名为xxx_chain, xxx_nofitier_chain这种形式的变量名。围绕核心数据结构notifier_block,内核定义了四种通知链类型:
1. 原子通知链( Atomic notifier chains ):通知链元素的回调函数(当事件发生时要执行的函数)在中断或原子操作上下文中运行,不允许阻塞。对应的链表头结构:
struct atomic_notifier_head {spinlock_t lock;struct notifier_block *head; };
2. 可阻塞通知链( Blocking notifier chains ):通知链元素的回调函数在进程上下文中运行,允许阻塞。对应的链表头:
struct blocking_notifier_head {struct rw_semaphore rwsem;struct notifier_block *head; };
3. 原始通知链( Raw notifierchains ):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。对应的链表头:
网络子系统就是该类型,通过以下宏实现head的初始化
static RAW_NOTIFIER_HEAD(netdev_chain); #define RAW_NOTIFIER_INIT(name) { \.head= NULL } #define RAW_NOTIFIER_HEAD(name) \ //调用他就好了 struct raw_notifier_head name = \RAW_NOTIFIER_INIT(name) 即: struct raw_notifier_head netdev_chain = {.head = NULL; }
而其回调函数的注册,比如向netdev_chain的注册函数:register_netdevice_notifier。
struct raw_notifier_head {struct notifier_block *head; };
4. SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体。对应的链表头:
struct srcu_notifier_head {struct mutex mutex;struct srcu_struct srcu;struct notifier_block *head; };
1.3. 运行机理
被通知一方(other_subsys_x)通过notifier_chain_register向特定的chain注册回调函数,并且一般而言特 定的子系统会用特定的notifier_chain_register包装函数来注册,比如路由子系统使用的是网络子系统 的:register_netdevice_notifier来注册他的notifier_block。
1.3.1. 向事件通知链注册的步骤
1. 申明struct notifier_block结构
2. 编写notifier_call函数
3. 调用特定的事件通知链的注册函数,将notifier_block注册到通知链中
如果内核组件需要处理够某个事件通知链上发出的事件通知,其就该在初始化时在该通知链上注册回调函数。
1.3.2. 通知子系统有事件发生
inet_subsys是通过notifier_call_chain来通知其他的子系统(other_subsys_x)的。
notifier_call_chain会按照通知链上各成员的优先级顺序执行回调函数(notifier_call_x);回调函数的执行现场在 notifier_call_chain进程地址空间;其返回值是NOTIFY_XXX的形式,在include/linux/notifier.h中:
#define NOTIFY_DONE 0x0000 /* 对事件视而不见 */ #define NOTIFY_OK 0x0001 /* 事件正确处理 */ #define NOTIFY_STOP_MASK 0x8000 /*由notifier_call_chain检查,看继续调用回调函数,还是停止,_BAD和_STOP中包含该标志 */ #define NOTIFY_BAD (NOTIFY_STOP_MASK|0x0002) /*事件处理出错,不再继续调用回调函数 */ /**Clean way to return from the notifier and stop further calls.*/ #define NOTIFY_STOP (NOTIFY_OK|NOTIFY_STOP_MASK) /* 回调出错,不再继续调用该事件回调函数 */
notifier_call_chain捕获并返回最后一个事件处理函数的返回值;注意:notifier_call_chain可能同时被不同的cpu调用,故而调用者必须保证互斥。
1.3.3. 事件列表
对于网络子系统而言,其事件常以NETDEV_XXX命名;描述了网络设备状态(dev->flags)、传送队列状态 (dev->state)、设备注册状态(dev->reg_state),以及设备的硬件功能特性(dev->features):
include/linux/notifier.h中
/* netdevice notifier chain */ #define NETDEV_UP 0x0001 /* 激活一个网络设备 */ #define NETDEV_DOWN 0x0002f /* 停止一个网络设备,所有对该设备的引用都应释放 */ #define NETDEV_REBOOT 0x0003 /* 检查到网络设备接口硬件崩溃,硬件重启 */ #define NETDEV_CHANGE 0x0004 /* 网络设备的数据包队列状态发生改变 */ #define NETDEV_REGISTER 0x0005 /*一个网络设备事例注册到系统中,但尚未激活 */ #define NETDEV_UNREGISTER 0x0006 /*网络设备驱动已卸载 */ #define NETDEV_CHANGEMTU 0x0007 /*MTU发生了改变 */ #define NETDEV_CHANGEADDR 0x0008 /*硬件地址发生了改变 */ #define NETDEV_GOING_DOWN 0x0009 /*网络设备即将注销,有dev->close报告,通知相关子系统处理 */ #define NETDEV_CHANGENAME 0x000A /*网络设备名改变 */ #define NETDEV_FEAT_CHANGE 0x000B /*feature网络硬件功能改变 */ #define NETDEV_BONDING_FAILOVER 0x000C /* */ #define NETDEV_PRE_UP 0x000D /* */ #define NETDEV_BONDING_OLDTYPE 0x000E /* */ #define NETDEV_BONDING_NEWTYPE 0x000F /* */
1.4. 简单一例:
通过上面所述,notifier_chain机制只能在内核个子系统间使用,因此,这里使用3个模块:test_notifier_chain_0、 test_notifier_chain_1、test_notifier_chain_2;当 test_notifier_chain_2通过module_init初始化模块时发出事件TESTCHAIN_2_INIT;然后 test_notifier_chain_1作出相应的处理:打印 test_notifier_chain_2正在初始化。
/* test_chain_0.c :0. 申明一个通知链;1. 向内核注册通知链;2. 定义事件; 3. 导出符号,因而必需最后退出*/#include <linux/notifier.h> #include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> /* printk() */ #include <linux/fs.h> /* everything() */#define TESTCHAIN_INIT 0x52U static RAW_NOTIFIER_HEAD(test_chain);/* define our own notifier_call_chain */ static int call_test_notifiers(unsigned long val, void *v) {return raw_notifier_call_chain(&test_chain, val, v); } EXPORT_SYMBOL(call_test_notifiers);/* define our own notifier_chain_register func */static int register_test_notifier(struct notifier_block *nb) {int err;err = raw_notifier_chain_register(&test_chain, nb);if(err)goto out;out:return err; }EXPORT_SYMBOL(register_test_notifier);static int __init test_chain_0_init(void) {printk(KERN_DEBUG "I'm in test_chain_0\n");return 0; }static void __exit test_chain_0_exit(void) {printk(KERN_DEBUG "Goodbye to test_chain_0\n"); // call_test_notifiers(TESTCHAIN_EXIT, (int *)NULL); }MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("fishOnFly");module_init(test_chain_0_init); module_exit(test_chain_0_exit);/* test_chain_1.c :1. 定义回调函数;2. 定义notifier_block;3. 向chain_0注册notifier_block;*/ #include <linux/notifier.h> #include <linux/module.h> #include <linux/init.h>#include <linux/kernel.h> /* printk() */ #include <linux/fs.h> /* everything() */extern int register_test_notifier(struct notifier_block *nb); #define TESTCHAIN_INIT 0x52U/* realize the notifier_call func */ int test_init_event(struct notifier_block *nb, unsigned long event,void *v) {switch(event){case TESTCHAIN_INIT:printk(KERN_DEBUG "I got the chain event: test_chain_2 is on the way of init\n");break;default:break;}return NOTIFY_DONE; } /* define a notifier_block */ static struct notifier_block test_init_notifier = {.notifier_call = test_init_event, }; static int __init test_chain_1_init(void) {printk(KERN_DEBUG "I'm in test_chain_1\n");register_test_notifier(&test_init_notifier);<span style="white-space:pre"> </span>// 由chain_0提供的设施return 0; }static void __exit test_chain_1_exit(void) {printk(KERN_DEBUG "Goodbye to test_clain_l\n"); }MODULE_LICENSE("GPL"); MODULE_AUTHOR("fishOnFly");module_init(test_chain_1_init); module_exit(test_chain_1_exit);/* test_chain_2.c:发出通知链事件*/#include <linux/notifier.h> #include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> /* printk() */ #include <linux/fs.h> /* everything() */extern int call_test_notifiers(unsigned long val, void *v); #define TESTCHAIN_INIT 0x52Ustatic int __init test_chain_2_init(void) {printk(KERN_DEBUG "I'm in test_chain_2\n");call_test_notifiers(TESTCHAIN_INIT, "no_use");return 0; }static void __exit test_chain_2_exit(void) {printk(KERN_DEBUG "Goodbye to test_chain_2\n"); }MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("fishOnFly");module_init(test_chain_2_init); module_exit(test_chain_2_exit);# Makefile# Comment/uncomment the following line to disable/enable debugging # DEBUG = y# Add your debugging flag (or not) to CFLAGS ifeq ($(DEBUG),y)DEBFLAGS = -O -g -DSCULL_DEBUG # "-O" is needed to expand inlines elseDEBFLAGS = -O2 endififneq ($(KERNELRELEASE),) # call from kernel build systemobj-m := test_chain_0.o test_chain_1.o test_chain_2.oelseKERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd)modules:$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesendifclean:rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versionsdepend .depend dep:$(CC) $(CFLAGS) -M *.c > .dependifeq (.depend,$(wildcard .depend)) include .depend endif[wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_0.ko [wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_1.ko [wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_2.ko[wang2@iwooing: notifier_chian]$ dmesg[ 5950.112649] I'm in test_chain_0 [ 5956.766610] I'm in test_chain_1 [ 5962.570003] I'm in test_chain_2 [ 5962.570008] I got the chain event: test_chain_2 is on the way of init[ 6464.042975] Goodbye to test_chain_2 [ 6466.368030] Goodbye to test_clain_l [ 6468.371479] Goodbye to test_chain_0
一、概述
内核许多子系统之间关联紧密,因此在一个子系统发生或者检测到的事件信息很可能对其他子系统来说也是有价值的。为了满足其他子系统对这些事件信息的需求,即在某个子系统内发生或检测到事件时,其他对此感兴趣的子系统也能知道事件的发生,内核提供了notification chain机制。
注意:notification chain适用于内核子系统之间的信息传递,不涉及用户态。
二、结构体
1. notifier_block
//action参数表示发生的事件类型,因为一个chain可能支持多个事件,此参数用来对事件进行区分
//void *data用来存放私有信息,其具体信息取决于特定的事件
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; //用于同一个chain中的notifier_block的链接int priority;//表示notifier_call函数的优先级,在事件发生时先调用高优先级的回调函数。 };
notifier_call返回值为int类型,可能的返回值(include/linux/notifier.h文件中定义了这些常值)
//对该事件不感兴趣 #define NOTIFY_DONE 0x0000 //成功响应该事件 #define NOTIFY_OK 0x0001 //该回调函数返回后停止处理后续notifier block #define NOTIFY_STOP_MASK 0x8000 //出错,回调函数返回后停止处理后续notifier block #define NOTIFY_BAD (NOTIFY_STOP_MASK|0x0002) //成功响应事件,回调函数返回后停止处理后续notifier block #define NOTIFY_STOP (NOTIFY_OK|NOTIFY_STOP_MASK)
2. 围绕核心数据结构notifier_block,内核定义了四种通知链类型:
1)原子通知链( Atomic notifier chains ):通知链元素的回调函数(当事件发生时要执行的函数)在中断或原子操作上下文中运行,不允许阻塞。对应的链表头结构:
struct atomic_notifier_head {spinlock_t lock;struct notifier_block __rcu *head; };
2)可阻塞通知链( Blocking notifier chains ):通知链元素的回调函数在进程上下文中运行,允许阻塞。对应的链表头:
struct blocking_notifier_head {struct rw_semaphore rwsem;struct notifier_block __rcu *head; };
3)原始通知链( Raw notifierchains ):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。对应的链表头:
struct raw_notifier_head {struct notifier_block __rcu *head; };
4)SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体。对应的链表头:
struct srcu_notifier_head {struct mutex mutex;struct srcu_struct srcu;struct notifier_block __rcu *head; };
三、操作过程
被通知一方(other_subsys_x)通过notifier_chain_register向特定的chain注册回调函数,并且一般而言特定的子系统会用特定的notifier_chain_register包装函数来注册,比如路由子系统使用的是网络子系统的:register_netdevice_notifier来注册他的notifier_block。
1.向事件通知链注册的步骤
(1)申明struct notifier_block结构
(2)编写notifier_call函数
(3)调用特定的事件通知链的注册函数,通过notifier_chain_register()将notifier_block注册到通知链中。实际上notification chain就是一组函数列表。通常notification chain的名字的格式为xxx_chain、xxx_notifier_chain、 xxx_notifier_list,例如reboot_notifier_list。
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;//将节点n链接到链表nl中的合适位置rcu_assign_pointer(*nl, n);return 0; }
2.通知子系统有事件发生
如果内核组件需要处理够某个事件通知链上发出的事件通知,其就该在初始化时在该通知链上注册回调函数。
inet_subsys是通过notifier_call_chain来通知其他的子系统的。notifier_call_chain会按照通知链上各成员的优先级顺序执行回调函数(notifier_call_x);回调函数的执行现场在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;//取通知链中的notifier_blocknb = 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)break;nb = next_nb;nr_to_call--;}return ret; }
该函数将一个通知块结构从通知链表中拆除:
static int notifier_chain_unregister(struct notifier_block **nl,struct notifier_block *n)
四、实例
1. 定义通知链并初始化
static struct srcu_notifier_head cpufreq_transition_notifier_list; srcu_init_notifier_head(&cpufreq_transition_notifier_list); 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; }
2. 定义结构体和回调函数
static struct notifier_block __clk_cpufreq_notifier_block = {.notifier_call = __clk_cpufreq_notifier };static int __clk_cpufreq_notifier(struct notifier_block *nb, unsigned long val, void *data) {printk("dump cpu freq\n",);return 0; }
2. 注册
调用封装函数cpufreq_register_notifier(&__clk_cpufreq_notifier_block,CPUFREQ_TRANSITION_NOTIFIER)注册
int cpufreq_register_notifier(struct notifier_block *nb, unsigned int list) {int ret;WARN_ON(!init_cpufreq_transition_notifier_list_called);switch (list) {case CPUFREQ_TRANSITION_NOTIFIER://将我们定义的notifier_block结构注册到cpufreq_transition_notifier_list链中ret = srcu_notifier_chain_register(&cpufreq_transition_notifier_list, nb);break;case CPUFREQ_POLICY_NOTIFIER:ret = blocking_notifier_chain_register(&cpufreq_policy_notifier_list, nb);break;default:ret = -EINVAL;}return ret; }
3.通知事件发生
void cpufreq_notify_transition(struct cpufreq_freqs *freqs, unsigned int state) {struct cpufreq_policy *policy;BUG_ON(irqs_disabled());freqs->flags = cpufreq_driver->flags;pr_debug("notification %u of frequency transition to %u kHz\n",state, freqs->new);policy = per_cpu(cpufreq_cpu_data, freqs->cpu);switch (state) {case CPUFREQ_PRECHANGE://......//向通知链进行事件通知,最终会调用notifier_call_chain()函数,最终执行回调函数srcu_notifier_call_chain(&cpufreq_transition_notifier_list,CPUFREQ_PRECHANGE, freqs);adjust_jiffies(CPUFREQ_PRECHANGE, freqs);break;//.......} }
本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sky-heaven/p/5357398.html,如需转载请自行联系原作者
Linux内核基础--事件通知链(notifier chain)good【转】相关推荐
- Linux内核基础--事件通知链(notifier chain)【转】
转自:http://blog.csdn.net/wuhzossibility/article/details/8079025 内核通知链 1.1. 概述 Linux内核中各个子系统相互依赖,当其中某个 ...
- Linux内核源码——通知链(notifier chain)
写在前面的话: 刚入手Linux驱动的时候,各种Linux内核的机制是我们需要搬起的一块大砖块.搬砖的过程中,我发现自己不能很好的理解相关的业务驱动的很大的一个原因就是对内核的基本机制掌握不太准确.刚 ...
- Linux内核通知链(notifier chain)
1.概述 Linux内核中各个子系统相互依赖,当其中某个子系统状态发生改变时,就必须使用一定的机制告知使用其服务的其他子系统,以便其他子系统采取相应的措施.为满足这样的需求,内核实现了事件通知链机制( ...
- linux 内核通知,[Linux] 内核通知链 notifier
Linux 内核中每个模块之间都是独立的,如果模块需要感知其他模块的事件,就需要用到内核通知链. 最典型的通知链应用就是 LCD 和 TP 之间,TP 需要根据 LCD 的亮灭来控制是否打开关闭触摸功 ...
- 深入理解Linux内核通知链(Notifier)
数据结构 内核使用struct notifier_block结构代表一个notifier typedef int (*notifier_fn_t)(struct notifier_block *nb, ...
- linux c 网络事件 通知,深入理解Linux网络技术内幕—通知链
内核的很多子系统之间具有很强的相互依赖关系,其中一个子系统发现的或产生的事件,其他子系统可能都有兴趣.为了实现这种交互需求,Linux使用了通知链(Notification Chain)机制. 通知链 ...
- linux内核基础和配置编译原理
2020-8-8 星期六 北京 闷热天 总结linux内核基础和配置编译原理,分两部分总结.仅作为技术积累,方便日后查阅.参考了网上的一些笔记. 第一部分:内核基础 2.14.1.内核和发行版的区别 ...
- 在linux基础上开发内核,科学院在Linux内核基础上,开发出中文操作系统是()。A.中科LinuxB.熊猫LinuxC.红旗Linux...
科学院在Linux内核基础上,开发出中文操作系统是().A.中科LinuxB.熊猫LinuxC.红旗Linux 更多相关问题 肉芽组织在光镜下主要由什么成份构成?A.炎症细胞及成纤维细胞B.成纤维细胞 ...
- Linux内核基础篇——常用调试技巧汇总
文章目录 printk 动态输出 BUG()和BUG_ON() dump_stack() devmem printk printk共有8个等级,从0-7,等级依次降低. 打印等级可以通过修改/proc ...
- linux 通知链,Linux内核通知链notifier
当有事件发生时,通知者调用 notifier_call_chain 函数通知事件的到达,这个函数会遍历n1指向的通知链中所有的元素,然后依次调用每一个的回调函数,完成通知动作. static int ...
最新文章
- java 构建是什么意思_构建的概念
- 自然语言处理:网购商品评论情感判定
- 阔步向前冲,拥抱云计算-【软件和信息服务】2012.10
- Microsoft Expression Blend Preview for Silverlight 5新版发布
- html5input输入框设置无边框_芯片充电两大改变,无看点的iPad8,上手体验发现并不简单!...
- PhpStorm 配置debug断点调试
- php ab压力测试,安装Xcache缓存加速php及ab压力测试结果
- 实战 | Vue + Element UI 页面创建
- Kotlin学习笔记 第二章 类与对象 第三节接口 第四节 函数式接口
- 2021陕西高考单招成绩查询,2020陕西高考高职单招成绩一分一段表高考成绩排名...
- 【python】10行代码下载B站弹幕
- CADD课程学习(12)-- 基于碎片的药物设计(MOE)
- Visio 流程图的箭头 设置
- 《PWM整流器及其控制》读书笔记-第二章-PWM整流器拓扑结构及原理
- 安卓版Qinmei 追番必备神器 缓冲快
- 2021-1-25计算机快速入门,简单掌握各种技巧
- A40i linux移植ssh报错
- 华为日历怎么显示一月_华为P20使用感受
- hdu3397 线段树
- 家用智能投影推荐 五千元档的当贝F5和当贝X3有哪些区别?
热门文章
- 分时电价模型,削峰填谷,转移24小时一天中用电率,减少谷峰差
- TODA项目Part1—后端项目设置与连接数据库
- 腾讯云表格识别Python-SDK使用
- solidity 函数修饰器 modifier
- 首发集团|中安未来赋能首发集团场景体验迭代升级
- 玩转华为ENSP模拟器系列 | 配置L3VdPdNd迭代SR-BE隧道示例
- SQL应用·:用SQL分析供应商营业额
- 微大夫感冒舒缓仪亮相“2018健康陕西发展大会”回顾
- java实现多个excel表格数据整合成一个excel表格
- 基于 图神经网络 + 知识图谱 的推荐系统 1