Linux内核基础--事件通知链(notifier chain)【转】
转自:http://blog.csdn.net/wuhzossibility/article/details/8079025
内核通知链
1.1. 概述
Linux内核中各个子系统相互依赖,当其中某个子系统状态发生改变时,就必须使用一定的机制告知使用其服务的其他子系统,以便其他子系统采取相应的措施。为满足这样的需求,内核实现了事件通知链机制(notificationchain)。
通知链只能用在各个子系统之间,而不能在内核和用户空间进行事件的通知。组成内核的核心系统代码均位于kernel目录下,通知链表位于kernel/notifier.c中,对应的头文件为include/linux/notifier.h。通知链表机制并不复杂,实现它的代码只有区区几百行。
事件通知链表是一个事件处理函数的列表,每个通知链都与某个或某些事件有关,当特定的事件发生时,就调用相应的事件通知链中的回调函数,进行相应的处理。

图 1 内核通知链
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 0x52U
- static 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
- else
- DEBFLAGS = -O2
- endif
- ifneq ($(KERNELRELEASE),)
- # call from kernel build system
- obj-m := test_chain_0.o test_chain_1.o test_chain_2.o
- else
- KERNELDIR ?= /lib/modules/$(shell uname -r)/build
- PWD := $(shell pwd)
- modules:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
- endif
- clean:
- rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
- depend .depend dep:
- $(CC) $(CFLAGS) -M *.c > .depend
- ifeq (.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
- <p></p>
Linux内核基础--事件通知链(notifier chain)【转】的更多相关文章
- Linux内核基础--事件通知链(notifier chain)
转载: http://blog.csdn.net/wuhzossibility/article/details/8079025 http://blog.chinaunix.net/uid-277176 ...
- Linux内核基础--事件通知链(notifier chain)good【转】
转自:http://www.cnblogs.com/pengdonglin137/p/4075148.html 阅读目录(Content) 1.1. 概述 1.2.数据结构 1.3. 运行机理 1. ...
- linux kernel notifier chain(事件通知链)
Linux内核中各个子系统相互依赖,当其中某个子系统状态发生改变时,就必须使用一定的机制告知使用其服务的其他子系统,以便其他子系统采取相应的措施.为满足这样的需求,内核实现了事件通知链机制(notif ...
- Linux内核基础优化
Linux内核基础优化 net.ipv4.ip_forward = 1 #开启网络转发 net.ipv4.conf.default.rp_filter = 0 #开启代理功能 net.ipv4.con ...
- [Linux] 内核通知链 notifier
Linux 内核中每个模块之间都是独立的,如果模块需要感知其他模块的事件,就需要用到内核通知链. 最典型的通知链应用就是 LCD 和 TP 之间,TP 需要根据 LCD 的亮灭来控制是否打开关闭触摸功 ...
- linux内核基础(系统调用,简明)
内核基础(系统调用) 在说系统调用之前.先来说说内核是怎么和我们交互的.或者说是怎么和我们产生交集的. 首先,内核是用来控制硬件的仅仅有内核才干直接控制硬件,所以说内核非常重要,假设内核被控制那么电脑 ...
- Linux内核基础
Linux系统运行的应用程序通过系统调用来与内核通信.应用程序通常调用库函数(比如C库函数)再有库函数通过系统调用界面,让内核带其完成各种不同的任务. 下面这张图显示的就是应用程序,内 ...
- Jquery | 基础 | 事件的链式写法
$(".title").click(function () { $(this).addClass("curcol").next(".content&q ...
- Linux 内核热插拔事件产生
一个热插拔事件是一个从内核到用户空间的通知, 在系统配置中有事情已经改变. 无论何 时一个 kobject 被创建或销毁就产生它们. 这样事件被产生, 例如, 当一个数字摄像头 使用一个 USB 线缆 ...
随机推荐
- BZOJ 1996 合唱队(DP)
考虑从最后的队形开始依次还原最初的队形. 对于当前的队形,要么选最左边的,要么选最右边的. 如果选了左边的,那么下次选择的一定是大于它的.右边的同理. 所以定义dp[mark][l][r]为区间[l, ...
- HUAS 1480 虫洞(最短路)
把每个点拆成两个点,分别表示黑洞和白洞,然后按题意模拟加边跑最短路即可. # include <cstdio> # include <cstring> # include &l ...
- Django 2.0 学习(05):Django Admin
Django Admin 站点的管理者,而非访问者. 创建admin用户 首先,执行下面命令: python manage.py createsuperuser 其次,输入用户名和密码: Userna ...
- 【刷题】BZOJ 3513 [MUTC2013]idiots
Description 给定n个长度分别为a_i的木棒,问随机选择3个木棒能够拼成三角形的概率. Input 第一行T(T<=100),表示数据组数. 接下来若干行描述T组数据,每组数据第一行是 ...
- CC DGCD:Dynamic GCD——题解
https://vjudge.net/problem/CodeChef-DGCD https://www.codechef.com/problems/DGCD 题目大意: 给一颗带点权的树,两个操作: ...
- 洛谷 P3952 时间复杂度 解题报告
P3952 时间复杂度 题目描述 小明正在学习一种新的编程语言A++,刚学会循环语句的他激动地写了好多程序并 给出了他自己算出的时间复杂度,可他的编程老师实在不想一个一个检查小明的程序, 于是你的机会 ...
- Linux之poll与select20160619
使用非阻塞I/O的应用程序通常会使用select()和poll()系统调用查询是否可对设备进行无阻塞的访问,这两个系统调用最终又会引发设备驱动中的poll()函数被执行 如果当前不可读(先调用驱动.p ...
- weakself的另一种写法
在不久前看AFNetworking的源码时候发现了这么一句: 1 2 3 4 5 6 7 8 9 10 // 不知道这行代码的使用场景的同学你该去自习看看ARC的注意事项和Block的使用了 // A ...
- HDU5957 Query on a graph(拓扑找环,BFS序,线段树更新,分类讨论)
传送门:http://acm.hdu.edu.cn/showproblem.php?pid=5957 题意:D(u,v)是节点u和节点v之间的距离,S(u,v)是一系列满足D(u,x)<=k的点 ...
- git与svn与github与码云的区别
1.git与github(https://www.oschina.net/)的区别 Git(https://git-scm.com/)是一个版本控制工具 github是一个用git做版本控制的项目托管 ...