Linux内核基础--事件通知链(notifier chain)
内核通知链
1.1. 概述
Linux内核中各个子系统相互依赖,当其中某个子系统状态发生改变时,就必须使用一定的机制告知使用其服务的其他子系统,以便其他子系统采取相应的措施。为满足这样的需求,内核实现了事件通知链机制(notificationchain)。
通知链只能用在各个子系统之间,而不能在内核和用户空间进行事件的通知。组成内核的核心系统代码均位于kernel目录下,通知链表位于kernel/notifier.c中,对应的头文件为include/linux/notifier.h。通知链表机制并不复杂,实现它的代码只有区区几百行。
事件通知链表是一个事件处理函数的列表,每个通知链都与某个或某些事件有关,当特定的事件发生时,就调用相应的事件通知链中的回调函数,进行相应的处理。
推荐阅读:
Linux内核驱动开发之KGDB原理介绍及kgdboe方式配置 http://www.linuxidc.com/Linux/2013-06/86406.htm
Linux内核中memcpy和memmove函数的区别和实现 http://www.linuxidc.com/Linux/2013-06/85344.htm
图 1
1.2.数据结构
如图 1中所示,Linux的网络子系统一共有3个通知链:表示ipv4地址发生变化时的inetaddr_chain;表示ipv6地址发生变化的inet6addr_chain;还有表示设备注册、状态变化的netdev_chain。
在这些链中都是一个个notifier_block结构:1
2
3
4
5struct 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
4struct atomic_notifier_head {
spinlock_t lock;
struct notifier_block *head;
};可阻塞通知链( Blocking notifier chains ):通知链元素的回调函数在进程上下文中运行,允许阻塞。对应的链表头:
1
2
3
4struct blocking_notifier_head {
struct rw_semaphore rwsem;
struct notifier_block *head;
};原始通知链( Raw notifierchains ):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。对应的链表头:
网络子系统就是该类型,通过以下宏实现head的初始化1
2
3
4
5
6
7
8
9
10static RAW_NOTIFIER_HEAD(netdev_chain);
.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的注册函数:1
2
3
4
5register_netdevice_notifier。
struct raw_notifier_head {
struct notifier_block *head;
};
- SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体。对应的链表头:
1
2
3
4
5struct 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. 向事件通知链注册的步骤
申明struct notifier_block结构
编写notifier_call函数
调用特定的事件通知链的注册函数,将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中:1
2
3
4
5
6
7
8
/*
*Clean way to return from the notifier and stop further calls.
*/
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中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/* netdevice notifier chain */
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正在初始化。
1 | /* test_chain_0.c :0. 申明一个通知链;1. 向内核注册通知链;2. 定义事件;3. 导出符号,因而必需最后退出*/ |
内核通知链>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
最近在看《深入理解Linux网络内幕》一书,学习了一下书中讲到的内核通知链方面的知识,写了一个读书笔记和一点代码来加深理解,希望能够对大家有一点帮助。内核通知链在网络方面得到了广泛的使用。
1.通知链表简介
大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣。为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子系统,Linux内核提供了通知链的机制。通知链表只能够在内核的子系统之间使用,而不能够在内核与用户空间之间进行事件的通知。
通知链表是一个函数链表,链表上的每一个节点都注册了一个函数。当某个事情发生时,链表上所有节点对应的函数就会被执行。所以对于通知链表来说有一个通知方与一个接收方。在通知这个事件时所运行的函数由被通知方决定,实际上也即是被通知方注册了某个函数,在发生某个事件时这些函数就得到执行。其实和系统调用signal的思想差不多。
2.通知链表数据结构
通知链表的节点类型为notifier_block,其定义如下:1
2
3
4
5
6
7
8
9
10
11struct notifier_block
{
int (*notifier_call)(struct notifier_block *self, unsigned long, void *);
struct notifier_block *next;
int priority;
};
其中最重要的就是notifier_call这个函数指针,表示了这个节点所对应的要运行的那个函数。next指向下一个节点,即当前事件发生时还要继续执行的那些节点。
3.注册通知链
在通知链注册时,需要有一个链表头,它指向这个通知链表的第一个元素。这样,之后的事件对该链表通知时就会根据这个链表头而找到这个链表中所有的元素。
注册的函数是:
int notifier_chain_register(struct notifier_block *nl, struct notifier_block n)
也即是将新的节点n加入到nl所指向的链表中去。
卸载的函数是:
int notifier_chain_unregister(strut notifier_block *nl, struct notifier_block n)
也即是将节点n从nl所指向的链表中删除。
4.通知链表
当有事件发生时,就使用notifier_call_chain向某个通知链表发送消息。
int notifier_call_chain(struct notifier_block *nl, unsigned long val, void v)
这个函数是按顺序运行nl指向的链表上的所有节点上注册的函数。简单地说,如下所示:
1 | struct notifier_block *nb = *n; |
5.示例
在这里,写了一个简单的通知链表的代码。
实际上,整个通知链的编写也就两个过程:
首先是定义自己的通知链的头节点,并将要执行的函数注册到自己的通知链中。
其次则是由另外的子系统来通知这个链,让其上面注册的函数运行。
我这里将第一个过程分成了两步来写,第一步是定义了头节点和一些自定义的注册函数(针对该头节点的),第二步则是使用自定义的注册函数注册了一些通知链节点。分别在代码buildchain.c与regchain.c中。 发送通知信息的代码为notify.c。
代码1 buildchain.c
它的作用是自定义一个通知链表test_chain,然后再自定义两个函数分别向这个通知链中加入或删除节点,最后再定义一个函数通知这个test_chain链。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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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这个通知链表上,同时每个节点都注册了一个函数。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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
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通知链中发送消息,让链中的函数运行。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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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文件
obj-m:=buildchain.o regchain.o notify.o
KERNELDIR:=/lib/modules/$(shell uname -r)/build
default:
make -C $(KERNELDIR) M=$(shell pwd) modules
运行:
make
insmod buildchain.ko
insmod regchain.ko
insmod notify.ko
这样就可以看到通知链运行的效果了
下面是我在自己的机器上面运行得到的结果:
引用:1
2
3
4
5
6
7
8
9
10init_notifier
Begin to register:
register test_notifier1 completed
register test_notifier2 completed
register test_notifier3 completed
Begin to notify:
==============================
In Event 1: Event Number is 1
In Event 2: Event Number is 1
In Event 3: Event Number is 1
来源:https://blog.csdn.net/cheng_fangang/article/details/17350261