Bootstrap

Linux 内核通知链(notifier chain)原理和使用方法

目录

通知链的引入

通知链类型

一个简单的测试实例


通知链的引入

Linux内核中各个子系统相互依赖,当其中某个子系统状态发生改变时,就必须使用一定的机制告知使用其服务的其他子系统,以便其他子系统采取相应的措施。为满足这样的需求,内核实现了事件通知链机制(notification chain)。

通知链只能用在各个子系统之间,而不能在内核和用户空间进行事件的通知。组成内核的核心系统代码均位于kernel目录下,通知链表位于kernel/notifier.c中,对应的头文件为include/linux/notifier.h。

事件通知链表是一个事件处理函数的列表,每个通知链都与某个或某些事件有关,当特定的事件发生时,就调用相应的事件通知链中的回调函数,进行相应的处理。
 

通知链类型

内核使用struct notifier_block结构代表一个notifier

typedef	int (*notifier_fn_t)(struct notifier_block *nb,
			unsigned long action, void *data);   //notifier 回调函数类型定义
 
struct notifier_block { 
	notifier_fn_t notifier_call;                  //回调函数
	struct notifier_block __rcu *next;            //用于挂到通知链上
	int priority;
};                                                //notifier 结构体

内核提供了四种不同类型的notifier chain

1.原子通知链 (Atomic notifier chains)

之所以被称为原子通知链,是因为通知链元素的回调函数(当事件发生时要执行的函数)在中断或原子操作上下文中运行,不允许阻塞。

struct atomic_notifier_head {
	spinlock_t lock;                  // 自旋锁保护通知链
	struct notifier_block __rcu *head;   //通知链元素的链表
};

notifier block  和 notifier chain的数据结构组织方式如下:

原子通知链对应的API

1.初始化一个原子通知链
#define ATOMIC_NOTIFIER_HEAD(name)				\
	struct atomic_notifier_head name =			\
		ATOMIC_NOTIFIER_INIT(name)

2.注册一个notifier block 到通知链
extern int atomic_notifier_chain_register(struct atomic_notifier_head *nh,
		struct notifier_block *nb);

3.发送一个事件到通知链上的notifier block
extern int atomic_notifier_call_chain(struct atomic_notifier_head *nh,
		unsigned long val, void *v);

4.从通知链删除一个notifier block
extern int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh,
		struct notifier_block *nb);

2. 可阻塞通知链( Blocking notifier chains ):通知链元素的回调函数在进程上下文中运行,允许阻塞。

struct  blocking_notifier_head {
        struct  rw_semaphore  rwsem;
        struct  notifier_block  *head;
};

 可阻塞通知链对应的API

1.初始化一个阻塞通知链
#define BLOCKING_NOTIFIER_HEAD(name)				\
	struct blocking_notifier_head name =			\
		BLOCKING_NOTIFIER_INIT(name)

2.注册一个notifier block 到通知链
extern int blocking_notifier_chain_register(struct blocking_notifier_head *nh,
		struct notifier_block *nb);

3.发送一个事件到通知链上的notifier block
extern int blocking_notifier_call_chain(struct blocking_notifier_head *nh,
		unsigned long val, void *v);

4.从通知链删除一个notifier block
extern int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh,
		struct notifier_block *nb);

3. 原始通知链( Raw notifierchains ):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。

struct  raw_notifier_head {
        struct  notifier_block  *head;
};

原始通知链对应的API

1.初始化一个原始通知链
#define RAW_NOTIFIER_HEAD(name)					\
	struct raw_notifier_head name =				\
		RAW_NOTIFIER_INIT(name)

2.注册一个notifier block 到通知链
extern int raw_notifier_chain_register(struct raw_notifier_head *nh,
		struct notifier_block *nb);

3.发送一个事件到通知链上的notifier block
extern int raw_notifier_call_chain(struct raw_notifier_head *nh,
		unsigned long val, void *v);

4.从通知链删除一个notifier block
extern int raw_notifier_chain_unregister(struct raw_notifier_head *nh,
		struct notifier_block *nb);

4. SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体。对应的链表头:

struct  srcu_notifier_head {
        struct  mutex mutex;
        struct  srcu_struct  srcu;
        struct  notifier_block  *head;
};

SRCU 通知链对应的API 

1.初始化一个SRCU通知链
#ifdef CONFIG_TREE_SRCU
#define _SRCU_NOTIFIER_HEAD(name, mod)				\
	static DEFINE_PER_CPU(struct srcu_data, name##_head_srcu_data); \
	mod struct srcu_notifier_head name =			\
			SRCU_NOTIFIER_INIT(name, name##_head_srcu_data)

#else
#define _SRCU_NOTIFIER_HEAD(name, mod)				\
	mod struct srcu_notifier_head name =			\
			SRCU_NOTIFIER_INIT(name, name)

#endif

2.注册一个notifier block 到通知链
extern int srcu_notifier_chain_register(struct srcu_notifier_head *nh,
		struct notifier_block *nb);

3.发送一个事件到通知链上的notifier block
extern int raw_notifier_call_chain(struct raw_notifier_head *nh,
		unsigned long val, void *v);

4.从通知链删除一个notifier block
extern int srcu_notifier_call_chain(struct srcu_notifier_head *nh,
		unsigned long val, void *v);

一个简单的测试实例

我们可以自己写一个内核模块验证一下通知链的使用方法。

1.模块代码

//hello.c   通知链测试模块
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/delay.h>

static RAW_NOTIFIER_HEAD(test_chain_head);   //定义通知链
#define EVENT_A 0x01    //定义通知链的事件类型
#define EVENT_B 0x02

//定义回调函数
int test_notifier_event(struct notifier_block *nb, unsigned long event, void *v)
{
    switch (event) {
    case EVENT_A:
        printk("test_notifier_event EVENT_A\n");
        break;
    case EVENT_B:
        printk("test_notifier_event EVENT_B\n");
        break;
    default:
        break;
    }

    return NOTIFY_DONE;
}

//定义notifier block
static struct notifier_block test_notifier = {
    .notifier_call = test_notifier_event,  //指定回调函数
};

static int __init mynotify_init(void)
{
    printk("raw_notifier_chain_register\n");
    raw_notifier_chain_register(&test_chain_head, &test_notifier);

    printk("raw_notifier_send_EVENT_B\n");
    raw_notifier_call_chain(&test_chain_head, EVENT_B, NULL);

   
    printk("raw_notifier_send_EVENT_A\n");
    raw_notifier_call_chain(&test_chain_head, EVENT_A, NULL);

    return 0;
}

static void __exit mynotify_exit(void)
{
    raw_notifier_chain_unregister(&test_chain_head, &test_notifier);
}

module_init(mynotify_init);
module_exit(mynotify_exit);

MODULE_AUTHOR("Bai Heng");
MODULE_LICENSE("GPL");

 2. Makefile 文件


obj-m += hello.o                         #由此目标文件生成模块
CURRENT_PATH := $(shell pwd)             #当前所在的路径
LINUX_KERNEL := $(shell uname -r)        #内核的版本号

#内核代码所在的路径
LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL)

#编译
all:
    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules

#清除编译生成的文件
clean:
    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

3.编译内核模块

在当前目录直接运行 make   
baiheng@baiheng-OptiPlex-3020:~/code/test$ make
最终会生成一个hello.ko的内核模块

 4.在ubuntu主机运行测试

第一步:安装内核模块
baiheng@baiheng-OptiPlex-3020:~/code/test$ sudo insmod hello.ko

第二步:查看内核log ,验证测试结果
baiheng@baiheng-OptiPlex-3020:~/code/test$ sudo dmesg -c
[499586.143347] raw_notifier_chain_register
[499586.143350] raw_notifier_send_EVENT_B
[499586.143351] test_notifier_event EVENT_B
[499586.143351] raw_notifier_send_EVENT_A
[499586.143352] test_notifier_event EVENT_A

第三步:卸载内核模块
baiheng@baiheng-OptiPlex-3020:~/code/test$ sudo rmmod hello

 

;