Linux notifier chain 内核通知链
notifier chain 出现原因
大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣。为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子系统,Linux内核提供了通知链的机制。通知链表只能够在内核的子系统之间使用,而不能够在内核与用户空间之间进行事件的通知。通知链表是一个函数链表,链表上的每一个节点都注册了一个函数。当某个事情发生时,链表上所有节点对应的函数就会被执行。
notifier chain 基本原理
看起来 notifier 机制那么的高大上,其实别被名字所吓到,notifier 机制其实是非常简单的,本质上只是单向链表的扩充,下面一起来看下 notifier 机制是如何实现相应功能的
- notifier 实现的源码位置 kernel/notifier.c
- 重要数据结构 struct notifier_block,内核通知链本质还是链表,该结构体的函数指针 notifier_call 需要我们去填充,priority 是优先级,在加入通知链表时候,会根据链表的优先级进行插入,这样子在遍历链表的时候就可以按照优先级进行遍历(专业说,在通知发生时,就可以进行按照优先级进行函数指针的回调操作)
typedef int (*notifier_fn_t)(struct notifier_block *nb, unsigned long action, void *data); struct notifier_block { notifier_fn_t notifier_call; struct notifier_block __rcu *next; int priority; };
notifier chain 使用
通知链包括了:
- atomic_notifier_head(通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,不允许阻塞)
- blocking_notifier_head (通知链元素的回调函数在进程上下文中运行,允许阻塞)
- raw_notifier_head (对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护)
- srcu_notifier_head (可阻塞通知链的一种变体)
说白了都只是基于 notifier_block 进行再一次的封装,下面以 raw_notifier_head 为例,来演示通知链是如何使用的:
- chain.c,对 kernel/notifier.c 中提供的函数进行初步的封装
//定义头头结点 static RAW_NOTIFIER_HEAD(test_chain); //注册函数,即将节点添加进链表 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); //遍历链表,即一次调用链表中的指针 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); 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);
- regchain.c 代码,用于注册 notifer 事件,其实就是插入链表,test_event* 初始化 notifier_block,之后插入链表
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 longevent, void *ptr) { printk("In Event 1: Event Number is %d\n",event); return 0; } static int test_event2(struct notifier_block *this, unsigned longevent, 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; } //填充 static struct notifier_block test_notifier1 = { .notifier_call = test_event1, }; static struct notifier_block test_notifier2 = { .notifier_call = test_event2, }; 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_notifier1completed\n"); err =register_test_notifier(&test_notifier2); if (err) { printk("register test_notifier2 error\n"); return-1; } printk("register test_notifier2completed\n"); err =register_test_notifier(&test_notifier3); if (err) { printk("register test_notifier3 error\n"); return-1; } printk("register test_notifier3completed\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);
- notify.c 代码,主要调用用 test_notifier_call_chain 就可以通知链表执行回调函数
extern int test_notifier_call_chain(unsigned long val, void*v); static int __init call_notifier(void) { int err; printk("Begin to notify:\n"); 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("Endnotify\n"); } module_init(call_notifier); module_exit(uncall_notifier);
- 打印结果:
init_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 ==============================
总结
notifer 机制使用其实很简单,RAW_NOTIFIER_HEAD 初始化链表头,raw_notifier_chain_register 注册进链表中(插入),raw_notifier_call_chain 按照 priority 进行函数执行(遍历)。当然了,如果我们需求实现的时候,要求通过 raw_notifier_call_chain 遍历时候,要通过不同参数执行不同的操作时,我们可以在链表中的回调函数中使用 switch 语句进行区分,这样子就可以进行区分。最后,一个问题,既然 notifer 机制是为了可以在不同的子系统中进行通知调用,那我每次进行通知,又不想每个函数被调用到时,该机制真的友好吗?试想,在每次遍历的耗时,我们又不想调用链表的每个回调函数,这里面带来的耗时操作对系统可不是很友好。所以 Linux 机制是否有其他可以替代?