0
点赞
收藏
分享

微信扫一扫

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. long action, void *data);

  3. struct notifier_block {
  4. notifier_fn_t notifier_call;
  5. struct notifier_block __rcu *next;
  6. int priority;
  7. };

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. #define ATOMIC_NOTIFIER_HEAD(name)              \
  11. struct atomic_notifier_head name =          \
  12. ATOMIC_NOTIFIER_INIT(name)
  13. #define BLOCKING_NOTIFIER_HEAD(name)                \
  14. struct blocking_notifier_head name =            \
  15. BLOCKING_NOTIFIER_INIT(name)
  16. #define RAW_NOTIFIER_HEAD(name)                 \
  17. struct raw_notifier_head name =             \
  18. 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. static int notifier_chain_register(struct notifier_block **nl,
  6. struct notifier_block *n)
  7. {
  8. while ((*nl) != NULL) {
  9. if (n->priority > (*nl)->priority)
  10. break;
  11. nl = &((*nl)->next);
  12. }
  13. n->next = *nl;
  14. rcu_assign_pointer(*nl, n);
  15. return 0;
  16. }

上述的操作就是通过判断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. int blocking_notifier_chain_register(struct blocking_notifier_head *nh,struct notifier_block *n)
  4. int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh,struct notifier_block *n)

  5. int raw_notifier_chain_register(struct raw_notifier_head *nh,struct notifier_block *n)
  6. int raw_notifier_chain_unregister(struct raw_notifier_head *nh,struct notifier_block *n)

  7. int srcu_notifier_chain_register(struct srcu_notifier_head *nh,struct notifier_block *n).
  8. 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. 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. nb = rcu_dereference_raw(*nl);

  20. while (nb && nr_to_call) {
  21. next_nb = rcu_dereference_raw(nb->next);
  22. //调用注册的回调函数

  23. if (nr_calls)
  24. (*nr_calls)++;

  25. if ((ret & NOTIFY_STOP_MASK) == NOTIFY_STOP_MASK)  //有停止的mask就返回,否则继续
  26. break;
  27. nb = next_nb;
  28. nr_to_call--;
  29. }
  30. return ret;
  31. }

同样内核也提供了四个不同类型的通知函数



[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. BLOCKING_NOTIFIER_HEAD(test_chain_head);
  5. EXPORT_SYMBOL_GPL(test_chain_head);

  6. int register_test_notifier(struct notifier_block *nb)
  7. {
  8. return blocking_notifier_chain_register(&test_chain_head, nb);
  9. }

  10. int unregister_test_notifier(struct  notifier_block *nb)
  11. {
  12. return blocking_notifier_chain_unregister(&test_chain_head, nb);
  13. }

  14. static int test_chain_notify(struct notifier_block *nb,unsigned long mode, void *_unused)
  15. {
  16. "notifier: test_chain_notify!\n");        //回调处理函数
  17. return 0;
  18. }

  19. static struct notifier_block test_chain_nb = {
  20. .notifier_call = test_chain_notify,
  21. };


  22. static int notifier_test_init(void)
  23. {
  24. "notifier: notifier_test_init!\n");
  25. //注册notifier事件

  26. return 0;
  27. }

  28. static void notifier_test_exit(void)
  29. {
  30. "notifier: notifier_test_exit!\n");
  31. unregister_test_notifier(&test_chain_nb);
  32. }

  33. module_init(notifier_test_init);
  34. module_exit(notifier_test_exit);
  35. 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. extern struct blocking_notifier_head test_chain_head;

  5. static int call_notifier_call_chain(unsigned long val)
  6. {
  7. int ret = blocking_notifier_call_chain(&test_chain_head, val, NULL);
  8. return notifier_to_errno(ret);
  9. }

  10. static int call_test_init(void)
  11. {
  12. "notifier: call_test_init!\n");
  13. //在init函数中触发事件

  14. return 0;
  15. }

  16. static void call_test_exit(void)
  17. {
  18. "notifier: call_test_exit!\n");
  19. }
  20. module_init(call_test_init);
  21. module_exit(call_test_exit);
  22. 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 条评论