参考文章:http://blog.chinaunix.net/uid-23069658-id-4360706.html
一、内核通知链意义
通知链的原型就是一个单向链表,内核提供的通知链机制主要用于不同子系统之间通信,基于事件和优先级。考虑这么一种场景:对于网卡驱动子系统来说,经常会发生的情况就是什么?网卡IP地址有变化,网卡状态有变化等等。那么如果有其他子系统,比如路由子系统对网卡IP地址变化这件事比较感兴趣,它该怎么去感知这件事儿呢?当然这种场景下,很多人第一直觉就是“订阅者-发布者”模型。不过确实是这样的,通知链机制可以算作是“订阅者-发布者”模型的一种。每个子系统都会有些一些重要事件,例如前面说的,网络驱动子系统网卡的事件,或者USB的状态事件等等,这些子系统都会提供一个自己的事件队列,这个队列都是其他函数提供的回调函数。当有事件发生时,子系统就会去遍历其事件队列上已经注册了的所有回调函数,这样就实现了“通知”的目的。
二、函数接口
2.1 被通知元素结构
在上面的图例中,链表中的没有元素表示被通知的对象,它的定义在include/linux/notifier.h下面
struct notifier_block { int (*notifier_call)(struct notifier_block *, unsigned long, void *);//事件发生时执行的函数 struct notifier_block __rcu *next; //该事件链表上的下一个被通知对象 int priority; //被通知对象的优先级 }; struct atomic_notifier_head { //原子通知链头 spinlock_t lock; //自旋锁,该通知链使用在中断上下文 struct notifier_block __rcu *head; }; struct blocking_notifier_head { //阻塞通知链头 struct rw_semaphore rwsem; //信号量,该通知链可用在可以睡眠的环境 struct notifier_block __rcu *head; }; struct raw_notifier_head { //原始通知链头,需要手动添加保护措施 struct notifier_block __rcu *head; }; struct srcu_notifier_head { struct mutex mutex; struct srcu_struct srcu; struct notifier_block __rcu *head; };Linux内核提供了三类通知链:原子通知链、阻塞通知链和原始通知链,它们的主要区别就是在执行通知链上的回调函数时是否有安全保护措施。
2.2 通知链表头的创建
通知链的链表头有两种创建方式:静态创建、动态创建。
ATOMIC_INIT_NOTIFIER_HEAD(name)//初始化链表通知头的保护锁 BLOCKING_INIT_NOTIFIER_HEAD(name) RAW_INIT_NOTIFIER_HEAD(name) static struct atomic_notifier_head dock_notifier_list; ATOMIC_INIT_NOTIFIER_HEAD(&dock_notifier_list;)
2.3 添加被通知对象
当通知链表头建立好后,就可以向它里面添加被通知元素了。
static int notifier_chain_register(struct notifier_block **nl, struct notifier_block *n); //针对所有的链表头 int atomic_notifier_chain_register(struct atomic_notifier_head *nh, struct notifier_block *nb); //针对原子链表头,对notifier_chain_register进行封装 int blocking_notifier_chain_register(struct blocking_notifier_head *nh, struct notifier_block *nb);//针对可阻塞链表头,对notifier_chain_register进行封装 int raw_notifier_chain_register(struct raw_notifier_head *nh,struct notifier_block *nb); //针对原始链表头,对notifier_chain_register进行封装2.4 删除被通知对象
static int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n); //针对所有的链表头 int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh, struct notifier_block *nb); //针对原子链表头,对notifier_chain_unregister进行封装 int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh, struct notifier_block *nb);//针对可阻塞链表头,对notifier_chain_unregister进行封装 int raw_notifier_chain_unregister(struct raw_notifier_head *nh,struct notifier_block *nb); //针对原始链表头,对notifier_chain_unregister进行封装2.5 事件发生通知接受者
static int __kprobes notifier_call_chain(struct notifier_block **nl, unsigned long val, void *v, int nr_to_call, int *nr_calls);//针对所有链表头 int atomic_notifier_call_chain(struct atomic_notifier_head *nh, unsigned long val, void *v); //针对原子链表头,对notifier_call_chain进行封装 int blocking_notifier_call_chain(struct blocking_notifier_head *nh,unsigned long val, void *v);//针对可阻塞链表头,对notifier_call_chain进行封装 int raw_notifier_call_chain(struct raw_notifier_head *nh, unsigned long val, void *v); //针对原始链表头,对notifier_call_chain进行封装整个内核链表的建立过程如下:
三、应用举例
应用场景:
事件产生者:某个内核模块,不断的获取当前系统时间,然后通过它的通知链表头,将产生时间事件通知监听者
事件监听者:根据自己的感兴趣的事件,将自己的通知节点注册到<事件产生者>所提供的<通知链表头>中
事件产生者源码:
#include <asm/uaccess.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/sched.h> #include <linux/notifier.h> #include <linux/init.h> #include <linux/types.h> #include <linux/module.h> #include <linux/kthread.h> #include <linux/delay.h> #include <linux/random.h> static RAW_NOTIFIER_HEAD(news_notifier_list); int register_news_notifier(struct notifier_block *nb) //向该内核通知链表头注册一个元素 { return raw_notifier_chain_register(&news_notifier_list, nb); } EXPORT_SYMBOL(register_news_notifier); int unregister_news_notifier(struct notifier_block *nb) //删除内核通知链表中的元素 { return raw_notifier_chain_unregister(&news_notifier_list, nb); } EXPORT_SYMBOL(unregister_news_notifier); int call_news_notifier_chain(unsigned long val, void *v) //通知链表里面的元素,val和v为传递给回调函数的参数,一个是值传递一个是地址传递 { return raw_notifier_call_chain(&news_notifier_list, val, v); } EXPORT_SYMBOL(call_news_notifier_chain); static int create_news_thread(void *data) //产生事件的源头 { int i = 10; struct timeval curTime; printk(KERN_ERR"-------create_news_thread start-------\n"); while(i--) { do_gettimeofday(&curTime); //系统当前时间 call_news_notifier_chain(curTime.tv_sec,NULL); //将产生的事件告诉通知链表头里面的元素 ssleep(2); //休眠,方便观察效果 } printk(KERN_ERR"-------create_news_thread end-------\n"); return 0; } static int __init news_init_notifier(void) { printk(KERN_ERR"-------news_init_notifier module init-------\n"); kthread_run(create_news_thread,NULL,"create_news_thread"); return 0; } static void __exit news_exit_notifier(void) { printk(KERN_ERR"-------news_init_notifier module exit-------\n"); } module_init(news_init_notifier); module_exit(news_exit_notifier); MODULE_LICENSE("GPL");
#include <asm/uaccess.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/notifier.h> #include <linux/init.h> #include <linux/types.h> #include <linux/module.h> extern int register_news_notifier(struct notifier_block*); extern int unregister_news_notifier(struct notifier_block*); static int printNews(struct notifier_block *this, unsigned long event, void *ptr) { printk(KERN_ERR"-------receiced curTime is : %l-------\n",event); return 1; } static struct notifier_block news_notifier = { .notifier_call = printNews, .priority = 2, }; static int __init listener1_register(void) { int err; printk(KERN_ERR"-------listener1 module init-------\n"); err = register_news_notifier(&news_notifier); if (err) { printk(KERN_ERR"Refused!\n"); return -1; } return err; } static void __exit listener1_unregister(void) { unregister_news_notifier(&news_notifier); printk(KERN_ERR"-------listener1 module exit-------\n"); } module_init(listener1_register); module_exit(listener1_unregister); MODULE_LICENSE("GPL");
KERN_DIR = 你的内核目录树所在目录 all: make ARCH=arm CC=arm-linux-gnueabihf-gcc LD=arm-linux-gnueabihf-ld -C $(KERN_DIR) M=`pwd` modules clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order obj-m += news_center.o listener1.o
/mnt/sdcard # insmod news_center.ko [ 42.980126] -------news_init_notifier module init------- [ 42.986713] -------create_news_thread start------- /mnt/sdcard # insmod listener1.ko [ 48.469126] -------listener1 module init------- /mnt/sdcard # [ 49.020070] -------receiced curTime is : 87829------- [ 51.030081] -------receiced curTime is : 87831------- [ 53.040070] -------receiced curTime is : 87833------- [ 55.050065] -------receiced curTime is : 87835------- [ 57.060067] -------receiced curTime is : 87837------- [ 59.070069] -------receiced curTime is : 87839------- [ 61.080076] -------receiced curTime is : 87841------- [ 63.090067] -------create_news_thread end-------