本文将介绍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;
};

其中:

  1. notifier_call:当相应事件发生时应该调用的函数,由被通知方提供,如other_subsys_1;

  2. notifier_block *next:用于链接成链表的指针;

  3. priority:回调函数的优先级,一般默认为0。

内核代码中一般把通知链命名为xxx_chain, xxx_nofitier_chain这种形式的变量名。围绕核心数据结构notifier_block,内核定义了四种通知链类型:

  1. 原子通知链(Atomic notifier chains):通知链元素的回调函数(当事件发生时要执行的函数)在中断或原子操作上下文中运行,不允许阻塞。对应的链表头结构:

    1
    2
    3
    4
    struct atomic_notifier_head {
    spinlock_t lock;
    struct notifier_block *head;
    };
  2. 可阻塞通知链(Blocking notifier chains):通知链元素的回调函数在进程上下文中运行,允许阻塞。对应的链表头:

    1
    2
    3
    4
    struct  blocking_notifier_head {
    struct rw_semaphore rwsem;
    struct notifier_block *head;
    };
  3. 原始通知链(Raw notifierchains):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。对应的链表头:

    1
    2
    3
    struct  raw_notifier_head {
    struct notifier_block *head;
    };
  4. 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 子系统向事件通知链注册的步骤

  1. 申明struct notifier_block结构

  2. 编写notifier_call函数

  3. 调用特定的事件通知链的注册函数,将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
/* 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 /* */

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
/* test_chain_0.c :0. 申明一个通知链;1. 向内核注册通知链;2. 定义事件; 3. 导出符号*/

#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);

/* 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");
}

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
/* 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>
#include <linux/fs.h>

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); // 由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);
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
/* test_chain_2.c:发出通知链事件*/

#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
# 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
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


参考资料:

  1. Linux内核基础–事件通知链
  2. Notification Chains in Linux Kernel