linux内核通知链notifier

前言

在linux内核中,各个子系统之间有很强的相互关系,某些子系统可能对其他子系统产生的事件比较感兴趣。因此内核引入了notifier机制,当然了notifier机制只能用在内核子系统之间,不能用在内核与应用层之间。比如当系统suspend的时候,就会使用到notifier机制来通知系统的内核线程进行suspend。

内核实现的notifier机制代码位于kernel/kernel/notifier.c,同时此机制的代码量也不是很多只有600行左右。

数据结构

内核使用struct notifier_block结构代表一个notifier
[cpp]  view plain  copy
  1. typedef int (*notifier_fn_t)(struct notifier_block *nb,  
  2.             unsigned long action, void *data);  
  3.   
  4. struct notifier_block {  
  5.     notifier_fn_t notifier_call;  
  6.     struct notifier_block __rcu *next;  
  7.     int priority;  
  8. };  
notifier_call:  代表当事件发生之后调用的回调函数。
next:             用来链接同一个类型的notifier。
priority:         notifier chain的优先级。对应的数字越大优先级越高,就优先执行。

同时内核也提供了四种不同类型的notifier chain
  • 原子通知链(Atomic notifier chains)
[cpp]  view plain  copy
  1. struct atomic_notifier_head {  
  2.     spinlock_t lock;  
  3.     struct notifier_block __rcu *head;  
  4. };  
可以看到原子notifier chain只是对notifier_block的一个封装。同时atomic notifier chain的回调函数需要运行在中断上下文/原子上下文中,而且不能睡眠。
很明显因为atomic_notifer_head其中的spin_lock的特点就是不能睡眠。
  • 可阻塞通知链(Blocking notifier chains)
[cpp]  view plain  copy
  1. struct blocking_notifier_head {  
  2.     struct rw_semaphore rwsem;  
  3.     struct notifier_block __rcu *head;  
  4. };  
blocking_notifier_head其中包含了读写信号量成员rwsem,而信号量的特定就是运行在进程上下文,而且还可以睡眠。同理Blocking notifier chains的回调函数特征一样。
  • 原始通知链(Raw notifier chains)
[cpp]  view plain  copy
  1. struct raw_notifier_head {  
  2.     struct notifier_block __rcu *head;  
  3. };  
raw_notifier_head的特点是对回调函数,register, unregister都没有任何限制,所有的保护机制都需要调用者维护。
  • SRCU通知链(SRCU notifier chains)
[cpp]  view plain  copy
  1. struct srcu_notifier_head {  
  2.     struct mutex mutex;  
  3.     struct srcu_struct srcu;  
  4.     struct notifier_block __rcu *head;  
  5. };  
SRCU通知链是block notifier chain的一种变体,采用SRCU(Sleepable Read-Copy Update)代替rw-semphore来保护chains

notifier chain初始化

内核提供了一套宏用来初始化各个类型的通知链
[cpp]  view plain  copy
  1. #define ATOMIC_INIT_NOTIFIER_HEAD(name) do {    \  
  2.         spin_lock_init(&(name)->lock);   \  
  3.         (name)->head = NULL;     \  
  4.     } while (0)  
  5. #define BLOCKING_INIT_NOTIFIER_HEAD(name) do {  \  
  6.         init_rwsem(&(name)->rwsem);  \  
  7.         (name)->head = NULL;     \  
  8.     } while (0)  
  9. #define RAW_INIT_NOTIFIER_HEAD(name) do {   \  
  10.         (name)->head = NULL;     \  
  11.     } while (0)  
以上是动态初始化各个类型的通知链,当然了有动态初始化,也就有静态初始化
[cpp]  view plain  copy
  1. #define ATOMIC_NOTIFIER_INIT(name) {                \  
  2.         .lock = __SPIN_LOCK_UNLOCKED(name.lock),    \  
  3.         .head = NULL }  
  4. #define BLOCKING_NOTIFIER_INIT(name) {              \  
  5.         .rwsem = __RWSEM_INITIALIZER((name).rwsem), \  
  6.         .head = NULL }  
  7. #define RAW_NOTIFIER_INIT(name) {               \  
  8.         .head = NULL }  
  9. /* srcu_notifier_heads cannot be initialized statically */  
  10.   
  11. #define ATOMIC_NOTIFIER_HEAD(name)              \  
  12.     struct atomic_notifier_head name =          \  
  13.         ATOMIC_NOTIFIER_INIT(name)  
  14. #define BLOCKING_NOTIFIER_HEAD(name)                \  
  15.     struct blocking_notifier_head name =            \  
  16.         BLOCKING_NOTIFIER_INIT(name)  
  17. #define RAW_NOTIFIER_HEAD(name)                 \  
  18.     struct raw_notifier_head name =             \  
  19.         RAW_NOTIFIER_INIT(name)  
通过注释可以知道SRCU通知链不能使用静态的方法,因此内核提供了一个动态的初始化函数,
[cpp]  view plain  copy
  1. void srcu_init_notifier_head(struct srcu_notifier_head *nh)  
  2. {  
  3.     mutex_init(&nh->mutex);  
  4.     if (init_srcu_struct(&nh->srcu) < 0)  
  5.         BUG();  
  6.     nh->head = NULL;  
  7. }  

注册/注销通知链

内核提供的最基本的注册通知链的函数
[cpp]  view plain  copy
  1. /* 
  2.  *  Notifier chain core routines.  The exported routines below 
  3.  *  are layered on top of these, with appropriate locking added. 
  4.  */  
  5.   
  6. static int notifier_chain_register(struct notifier_block **nl,  
  7.         struct notifier_block *n)  
  8. {  
  9.     while ((*nl) != NULL) {  
  10.         if (n->priority > (*nl)->priority)  
  11.             break;  
  12.         nl = &((*nl)->next);  
  13.     }  
  14.     n->next = *nl;  
  15.     rcu_assign_pointer(*nl, n);  
  16.     return 0;  
  17. }  
上述的操作就是通过判断priority的大小,然后将大的插入带链表头,小的插入在链表末尾。
[cpp]  view plain  copy
  1. static int notifier_chain_unregister(struct notifier_block **nl,  
  2.         struct notifier_block *n)  
  3. {  
  4.     while ((*nl) != NULL) {  
  5.         if ((*nl) == n) {  
  6.             rcu_assign_pointer(*nl, n->next);  
  7.             return 0;  
  8.         }  
  9.         nl = &((*nl)->next);  
  10.     }  
  11.     return -ENOENT;  
  12. }  
上述的注销函数,就是先找到此节点,然后从链表中删除的一个操作。
因为插入/删除操作都是临界资源,需要使用rcu机制保护起来。
同样,内核通过包装核心的注册/注销函数,实现了上述说的四种notifier chain
[cpp]  view plain  copy
  1. int atomic_notifier_chain_register(struct atomic_notifier_head *nh,struct notifier_block *n)  
  2. int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh,struct notifier_block *n)  
  3.   
  4. int blocking_notifier_chain_register(struct blocking_notifier_head *nh,struct notifier_block *n)  
  5. int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh,struct notifier_block *n)  
  6.   
  7. int raw_notifier_chain_register(struct raw_notifier_head *nh,struct notifier_block *n)  
  8. int raw_notifier_chain_unregister(struct raw_notifier_head *nh,struct notifier_block *n)  
  9.   
  10. int srcu_notifier_chain_register(struct srcu_notifier_head *nh,struct notifier_block *n).  
  11. int srcu_notifier_chain_unregister(struct srcu_notifier_head *nh,struct notifier_block *n)  

通知函数

当某种事件需要发生的时候,就需要调用内核提供的通知函数notifier call函数,来通知注册过相应时间的子系统。
[cpp]  view plain  copy
  1. /** 
  2.  * notifier_call_chain - Informs the registered notifiers about an event. 
  3.  *  @nl:        Pointer to head of the blocking notifier chain 
  4.  *  @val:       Value passed unmodified to notifier function 
  5.  *  @v:     Pointer passed unmodified to notifier function 
  6.  *  @nr_to_call:    Number of notifier functions to be called. Don't care 
  7.  *          value of this parameter is -1. 
  8.  *  @nr_calls:  Records the number of notifications sent. Don't care 
  9.  *          value of this field is NULL. 
  10.  *  @returns:   notifier_call_chain returns the value returned by the 
  11.  *          last notifier function called. 
  12.  */  
  13. static int notifier_call_chain(struct notifier_block **nl,  
  14.                    unsigned long val, void *v,  
  15.                    int nr_to_call, int *nr_calls)  
  16. {  
  17.     int ret = NOTIFY_DONE;  
  18.     struct notifier_block *nb, *next_nb;  
  19.   
  20.     nb = rcu_dereference_raw(*nl);  
  21.   
  22.     while (nb && nr_to_call) {  
  23.         next_nb = rcu_dereference_raw(nb->next);  
  24.         ret = nb->notifier_call(nb, val, v);          //调用注册的回调函数  
  25.   
  26.         if (nr_calls)  
  27.             (*nr_calls)++;  
  28.   
  29.         if ((ret & NOTIFY_STOP_MASK) == NOTIFY_STOP_MASK)  //有停止的mask就返回,否则继续  
  30.             break;  
  31.         nb = next_nb;  
  32.         nr_to_call--;  
  33.     }  
  34.     return ret;  
  35. }  
同样内核也提供了四个不同类型的通知函数
[cpp]  view plain  copy
  1. int atomic_notifier_call_chain(struct atomic_notifier_head *nh,unsigned long val, void *v)  
  2. int blocking_notifier_call_chain(struct blocking_notifier_head *nh,unsigned long val, void *v)  
  3. int raw_notifier_call_chain(struct raw_notifier_head *nh,unsigned long val, void *v)  
  4. int srcu_notifier_call_chain(struct srcu_notifier_head *nh,unsigned long val, void *v)  

示例分析

通过编写两个文件,一个用来注册事件,另一个用来通知事件。
notifier.c用来注册事件
[cpp]  view plain  copy
  1. #include <linux/kernel.h>  
  2. #include <linux/module.h>  
  3. #include <linux/notifier.h>  
  4.   
  5.   
  6. BLOCKING_NOTIFIER_HEAD(test_chain_head);  
  7. EXPORT_SYMBOL_GPL(test_chain_head);  
  8.   
  9. int register_test_notifier(struct notifier_block *nb)  
  10. {  
  11.     return blocking_notifier_chain_register(&test_chain_head, nb);  
  12. }  
  13.   
  14. int unregister_test_notifier(struct  notifier_block *nb)  
  15. {  
  16.     return blocking_notifier_chain_unregister(&test_chain_head, nb);  
  17. }  
  18.   
  19. static int test_chain_notify(struct notifier_block *nb,unsigned long mode, void *_unused)  
  20. {  
  21.     printk(KERN_EMERG "notifier: test_chain_notify!\n");        //回调处理函数  
  22.     return 0;  
  23. }  
  24.   
  25. static struct notifier_block test_chain_nb = {  
  26.     .notifier_call = test_chain_notify,  
  27. };  
  28.   
  29.   
  30. static int notifier_test_init(void)  
  31. {  
  32.     printk(KERN_EMERG "notifier: notifier_test_init!\n");          
  33.     register_test_notifier(&test_chain_nb);                       //注册notifier事件  
  34.   
  35.     return 0;  
  36. }  
  37.   
  38. static void notifier_test_exit(void)  
  39. {  
  40.     printk(KERN_EMERG "notifier: notifier_test_exit!\n");  
  41.     unregister_test_notifier(&test_chain_nb);  
  42. }  
  43.   
  44. module_init(notifier_test_init);  
  45. module_exit(notifier_test_exit);  
  46. MODULE_LICENSE("GPL v2");  

call.c用来触发事件。
[cpp]  view plain  copy
  1. #include <linux/kernel.h>  
  2. #include <linux/module.h>  
  3. #include <linux/notifier.h>  
  4.   
  5. extern struct blocking_notifier_head test_chain_head;  
  6.   
  7. static int call_notifier_call_chain(unsigned long val)  
  8. {  
  9.     int ret = blocking_notifier_call_chain(&test_chain_head, val, NULL);  
  10.     return notifier_to_errno(ret);  
  11. }  
  12.   
  13. static int call_test_init(void)  
  14. {  
  15.     printk(KERN_EMERG "notifier: call_test_init!\n");  
  16.     call_notifier_call_chain(123); //在init函数中触发事件  
  17.   
  18.     return 0;  
  19. }  
  20.   
  21. static void call_test_exit(void)  
  22. {  
  23.     printk(KERN_EMERG "notifier: call_test_exit!\n");  
  24. }  
  25. module_init(call_test_init);  
  26. module_exit(call_test_exit);  
  27. MODULE_LICENSE("GPL v2");  
测试结构如下:
[cpp]  view plain  copy
  1. root@test:/data # insmod notifier.ko                                           
  2. root@test:/data # insmod call.ko                                      
  3. root@test:/data # dmesg | grep "notifier"                             
  4. [   89.644596] c7 notifier: notifier_test_init!  
  5. [   95.956801] c6 notifier: call_test_init!  
  6. [   95.960624] c6 notifier: test_chain_notify!  
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值