本文将介绍Linux内核基础–事件通知链。
内容主要转载自:Linux内核基础–事件通知链。
1. 概述
Linux内核中各个子系统相互依赖,当其中某个子系统状态发生改变时,就必须使用一定的机制告知使用其服务的其他子系统,以便其他子系统采取相应的措施。为满足这样的需求,内核实现了事件通知链机制(notification chain)。
通知链只能用在各个子系统之间,而不能在内核态和用户态之间进行事件通知。内核的核心代码均位于kernel目录下,通知链表位于kernel/notifier.c中,对应的头文件为include/linux/notifier.h。
事件通知链是一个事件处理函数的列表,每个通知链都与某个或某些事件有关。当特定的事件发生时,就调用相应的事件通知链中的回调函数,进行相应的处理。
图 1 内核通知链
2. 数据结构
如图 1中所示,Linux的网络子系统一共有3个通知链:表示ipv4地址发生变化时的inetaddr_chain;表示ipv6地址发生变化的inet6addr_chain;还有表示设备注册、状态变化的netdev_chain。
在这些链中都是一个个notifier_block结构:
1 2 3 4 5
| struct notifier_block { int (*notifier_call)(struct notifier_block *, unsigned long, void *); struct notifier_block *next; int priority; };
|
其中:
notifier_call
:当相应事件发生时应该调用的函数,由被通知方提供,如other_subsys_1;
notifier_block *next
:用于链接成链表的指针;
priority
:回调函数的优先级,一般默认为0。
内核代码中一般把通知链命名为xxx_chain, xxx_nofitier_chain这种形式的变量名。围绕核心数据结构notifier_block
,内核定义了四种通知链类型:
原子通知链(Atomic notifier chains):通知链元素的回调函数(当事件发生时要执行的函数)在中断或原子操作上下文中运行,不允许阻塞。对应的链表头结构:
1 2 3 4
| struct atomic_notifier_head { spinlock_t lock; struct notifier_block *head; };
|
可阻塞通知链(Blocking notifier chains):通知链元素的回调函数在进程上下文中运行,允许阻塞。对应的链表头:
1 2 3 4
| struct blocking_notifier_head { struct rw_semaphore rwsem; struct notifier_block *head; };
|
原始通知链(Raw notifierchains):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。对应的链表头:
1 2 3
| struct raw_notifier_head { struct notifier_block *head; };
|
SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体。对应的链表头:
1 2 3 4 5
| struct srcu_notifier_head { struct mutex mutex; struct srcu_struct srcu; struct notifier_block *head; };
|
3. 运行机理
被通知一方(other_subsys_x)通过notifier_chain_register向特定的chain注册回调函数,并且一般而言特定的子系统会用特定的notifier_chain_register包装函数来注册。
3.1 子系统向事件通知链注册的步骤
申明struct notifier_block结构
编写notifier_call函数
调用特定的事件通知链的注册函数,将notifier_block注册到通知链中
3.2 通知子系统有事件发生
inet_subsys是通过notifier_call_chain来通知其他的子系统(other_subsys_x)的。
notifier_call_chain会按照通知链上各成员的优先级顺序执行回调函数(notifier_call_x)。
3.3 事件列表
对于网络子系统而言,其事件常以NETDEV_XXX命名:描述了网络设备状态(dev->flags)、传送队列状态(dev->state)、设备注册状态(dev->reg_state),以及设备的硬件功能特性(dev->features)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #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 #define NETDEV_CHANGEADDR 0x0008 #define NETDEV_GOING_DOWN 0x0009 #define NETDEV_CHANGENAME 0x000A #define NETDEV_FEAT_CHANGE 0x000B #define NETDEV_BONDING_FAILOVER 0x000C #define NETDEV_PRE_UP 0x000D #define NETDEV_BONDING_OLDTYPE 0x000E #define NETDEV_BONDING_NEWTYPE 0x000F
|
4. demo
notifier_chain机制只能在内核个子系统间使用,因此,这里使用3个模块:test_notifier_chain_0、test_notifier_chain_1、test_notifier_chain_2。
test_notifier_chain_2通过module_init初始化时会发出事件TESTCHAIN_INIT;然后 test_notifier_chain_1作出相应的处理(打印 test_notifier_chain_2正在初始化)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #include <linux/notifier.h> #include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/fs.h> #define TESTCHAIN_INIT 0x52U static RAW_NOTIFIER_HEAD(test_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);
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"); } MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("fishOnFly"); module_init(test_chain_0_init); module_exit(test_chain_0_exit);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include <linux/notifier.h> #include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/fs.h> extern int register_test_notifier(struct notifier_block *nb); #define TESTCHAIN_INIT 0x52U
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; }
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); 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);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include <linux/notifier.h> #include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/fs.h> 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);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
ifeq ($(DEBUG),y) DEBFLAGS = -O -g -DSCULL_DEBUG else DEBFLAGS = -O2 endif ifneq ($(KERNELRELEASE),)
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
|
1 2 3 4 5 6
| insmod test_chain_0.ko insmod test_chain_1.ko insmod test_chain_2.ko rmmod test_chain_2.ko rmmod test_chain_1.ko rmmod test_chain_0.ko
|
参考资料:
- Linux内核基础–事件通知链
- Notification Chains in Linux Kernel