Sholck

不积跬步,无以至千里.不积小流,无以成江海

0%

linux-通知链notifier学习

linux-通知链学习

实现

通知链notifier的实现为:A模块实现维护一个具备高优先级顺序的单向节点链表notifier,每一个节点有对应的函数指针和优先级。内核中的其他子系统模块B,C 如果对A中的事件感兴趣,那么需要向notifier上挂在一个子节点,并在此节点上注册回调。当事件发生时,遍历notifier上所有节点,并通过回调通知模块B,C,然后根据事件进行对应的处理。

作用

基础模块通信

理解

即A模块先获取各模块的回调并逐一调用,基础还是用到EXPORT_SYMBOL导出的function symbol和链表基础知识,理解实现偏简单

实践

代码

test-A 模块是通知链的维护者,原理上也应该是事件的生产者才对,但是本例中没有产生。
test-B 对模块test-A的事件感兴趣
test-C 产生事件(替代A模块产生)

test-A.c
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
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/notifier.h>
#include <linux/sched.h>
//#include <linux/init_task.h>

static RAW_NOTIFIER_HEAD(notifier_test);
static struct task_struct *p;

static int call_test_notifier(unsigned long action, void *data) {
return raw_notifier_call_chain(&notifier_test, action, data);
}

EXPORT_SYMBOL(call_test_notifier);

static int register_test_notifier(struct notifier_block *nh) {
return raw_notifier_chain_register(&notifier_test, nh);
}

EXPORT_SYMBOL(register_test_notifier);

static int __init notifier_test_A_init(void) {
p = current;
printk("enter %s pid is %d\n", __func__, p->pid);
return 0;
}

static void __exit notifier_test_A_exit(void) {
printk("exit %s \n", __func__);
}

module_init(notifier_test_A_init);
module_exit(notifier_test_A_exit);

MODULE_LICENSE("GPL");
test-B.c
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
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/notifier.h>
#include <linux/sched.h>
#include "test.h"

extern int register_test_notifier(struct notifier_block *nb);
static struct task_struct *p;
static int notifier_test_deal_0(struct notifier_block *nb, unsigned long action, void *val) {
printk("enter %s \n", __func__);
switch(action) {
case FIRST_EVENT:
printk("action is 0x%lu in func is %s\n", action, __func__);
break;
case SECOND_EVENT:
printk("aciotn is 0x%lu in func is %s\n", action, __func__);
break;
default:
printk("nothing to do\n");
break;
}
return 0;
}

static int notifier_test_deal_1(struct notifier_block *nb, unsigned long action, void *val) {
printk("enter %s \n", __func__);
switch(action) {
case FIRST_EVENT:
printk("action is 0x%lu in func is %s\n", action, __func__);
break;
case SECOND_EVENT:
printk("aciotn is 0x%lu in func is %s\n", action, __func__);
break;
default:
printk("nothing to do\n");
break;
}
return 0;
}

struct notifier_block test_nb_0 = {
.notifier_call = notifier_test_deal_0,
.next = NULL,
.priority = 0,
};

struct notifier_block test_nb_1 = {
.notifier_call = notifier_test_deal_1,
.next = NULL,
.priority = 1,
};

static int __init notifier_test_B_init(void) {
int ret;
p = current;
printk("enter %s pid is %d\n", __func__, p->pid);
ret = register_test_notifier(&test_nb_0);
if(ret < 0)
goto err;
ret = register_test_notifier(&test_nb_1);
err:
return ret;

}

static void __exit notifier_test_B_exit(void) {
printk("exit %s \n", __func__);
}

module_init(notifier_test_B_init);
module_exit(notifier_test_B_exit);

MODULE_LICENSE("GPL");
test-C.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/notifier.h>
#include <linux/sched.h>

#include "test.h"
static struct task_struct *p;
extern int call_test_notifier(unsigned long action, void *data);

static int __init notifier_test_C_init(void) {
p = current;
printk("enter %s pid is %d\n", __func__, p->pid);
return call_test_notifier(FIRST_EVENT, "no use");
}

static void __exit notifier_test_C_exit(void) {
printk("exit %s \n", __func__);
}

module_init(notifier_test_C_init);
module_exit(notifier_test_C_exit);

MODULE_LICENSE("GPL");
Makefile
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
MODULE_SRCS += test-A.c test-B.c test-C.c 

MODULE_OBJS += $(wildcard *.o) \
$(wildcard *.ko) \
$(wildcard *.mod) \
$(wildcard *.mod.c)

OUT_FILES += modules.order \
Module.symvers

#$(info "OBJS is $(MODULE_OBJS)")

obj-m += test-A.o test-B.o test-C.o
module-objs := test-A.o test-B.o test-C.o

#The path of kernel code
KDIR := /github/linux
PWD ?= $(shell pwd)

build: kernel_modules
kernel_modules:
make -C $(KDIR) M=$(PWD) modules

clean:
rm -rf $(MODULE_OBJS)
rm -rf $(OUT_FILES)

运行

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
/lib/modules/5.17.0+ # depmod  >>模块依赖检查

/lib/modules/5.17.0+ # cat modules.dep.bb
test-C.ko
test_A


test-A.ko symbol:call_test_notifier symbol:register_test_notifier

test-B.ko
test_A

/lib/modules/5.17.0+ # modprobe test-B >>test-B依赖test-A,所以test-A先插入,再test-B
[ 5247.717829] enter notifier_test_A_init pid is 131
[ 5247.720032] enter notifier_test_B_init pid is 131

/lib/modules/5.17.0+ # modprobe test-C
[ 5252.538503] enter notifier_test_C_init pid is 133
[ 5252.539397] enter notifier_test_deal_1 >>此节点上的优先级高,所以链表位置靠前,尽管插入链表时间靠后
[ 5252.539785] action is 0x1 in func is notifier_test_deal_1
[ 5252.540306] enter notifier_test_deal_0 >>此节点上的优先级低
[ 5252.540636] action is 0x1 in func is notifier_test_deal_0


/lib/modules/5.17.0+ # modprobe -r test-B >>模块删除,test-A被test-C依赖,无法移除,但是可以视为counts-1
[ 5266.963632] exit notifier_test_B_exit
modprobe: remove 'test_A': Resource temporarily unavailable
/lib/modules/5.17.0+ # modprobe -r test-C
[ 5269.053006] exit notifier_test_C_exit
[ 5269.067407] exit notifier_test_A_exit >>依赖数为0, 开始unload

整体结构

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
==========================================     |
test-A |
================================ |
chain: notifier_test |
---------- |
| | |
| head -------------- |
| | | |
----------- | |
=============================== | |
test_nb_1 <--------| |
-------------- |
| | cb |
| notifier_all ----------------------------->test-B.c:notifier_test_deal_1
| | |
-------------- |
| | |
| next -------------- |
| | | |
------------- | |
| | | |
| priority=1 | | <-------------test-B 通过调用test-A EXPORT_SYMBOL的函数将节点插入notifier chain
| | | |
------------- | |
================================= | |
test_nb_0 <-----------| |
-------------- |
| | cb |
| notifier_all ----------------------------->test-B.c:notifier_test_deal_0
| | |
-------------- |
| | |
| next=NULL | |
| | <-------------test-C 通过调用test-A EXPORT_SYMBOL的函数代替test-A产生事件
------------- |
| | |
| priority=0 | |
| | |
------------- |
=================================
==========================================

原理学习

notifyer代码实现为include/linux/notifier.hkernel/notifier.c两部分,简单包括结构声明,节点注册,事件通知

notifier 结构

主要为两个结构

  • 一个为raw_notifier_head, 抽象为整个链表,可以指向notifier的头节点
    1
    2
    3
    struct raw_notifier_head {                                                                                                                                                                                          
    struct notifier_block __rcu *head;
    };
  • 一个为notifier_block, 抽象为头节点
    1
    2
    3
    4
    5
    struct notifier_block {                                                                                                                                                                                             
    notifier_fn_t notifier_call; >>事件回调
    struct notifier_block __rcu *next; >>指向下一个节点,初始化为NULL
    int priority; >>节点优先级
    };

节点注册

实现为在优先级链表中插入节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static int notifier_chain_register(struct notifier_block **nl,                                                                                                                                                      
struct notifier_block *n)
{
while ((*nl) != NULL) {
if (unlikely((*nl) == n)) {
WARN(1, "notifier callback %ps already registered",
n->notifier_call);
return -EEXIST;
}
if (n->priority > (*nl)->priority)
break;
nl = &((*nl)->next);
}
n->next = *nl;
rcu_assign_pointer(*nl, n);
return 0;
}

事件通知

实现为遍历各节点并回调

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
static int 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_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)
break;
nb = next_nb;
nr_to_call--;
}
return ret;
}
NOKPROBE_SYMBOL(notifier_call_chain);

扩展

  1. unlikely
  2. RCU实现原理,函数包括rcu_assign_pointer, rcu_dereference_raw
  3. NOKPROBE_SYMBOL kprobe不可探测?