目录
通知链的引入
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