Linux 内核通知链随笔【上】

7250阅读 8评论2014-07-18 wjlkoorey258
分类:LINUX

    在阅读内核源码的时候,到处会看到通知链的身影。从技术上来讲,这并不是一个多么复杂、高深、难懂的部分,说白了就是一个单向链表的插入、删除和遍历等操作。但这部分是由协议栈头号大Boss----Alan Cox亲自主刀,足以说明这个基础特性的重要性,也有很多值得我们学习的地方。内核中通知链的基础文件就两个,头文件include/linux/notifier.h,源文件kernel/notifier.c,头文件和源文件所有代码加起来不超过1000行,总体来说还是比较好懂。
   刚才说过,通知链的原型就是一个单向链表,内核提供的通知链机制主要用于不同子系统之间通信,基于事件和优先级。往通俗里将,考虑这么一种场景:对于网卡驱动子系统来说,经常会发生的情况就是什么?网卡IP地址有变化,网卡状态有变化等等。那么如果有其他子系统,比如路由子系统最网卡IP地址变化这件事比较感兴趣,它该怎么去感知这件事儿呢?当然这种场景下,很多人第一直觉就是“订阅者-发布者”模型。不过确实是这样的,通知链机制可以算作是“订阅者-发布者”模型的一种。每个子系统都会有些一些重要事件,例如前面说的,网络驱动子系统网卡的事件,或者USB的状态事件等等,这些子系统都会提供一个自己的事件队列,这个队列都是其他函数提供的回调函数。当有事件发生时,子系统就会去遍历其事件队列上已经注册了的所有回调函数,这样就实现了“通知”的目的。说的云里雾里的,还是看图吧:
   对系统A来说,它自己的通知队列上被被人注册了三个回调函数,那么当系统A的某个事件发生时,它必须去遍历自己的事件队列headA,然后依次去执行队列里每个回调函数(这么说不太准确,不一定每个函数都执行,后面解释)。对子系统B来说,情况是一样地。

   内核里通知链队列里,每个元素都是一个通知块,原型如下:

点击(此处)折叠或打开

  1. /* include/linux/notifier.h*/
  2. struct notifier_block {
  3.     int (*notifier_call)(struct notifier_block *, unsigned long, void *);
  4.     struct notifier_block *next;
  5.     int priority;
  6. };

    notifier_call是回调函数的指针,指向的函数是当事件发生时要执行的函数;next指向下一个回调函数的通知块;priority是事件发生时本函数(由notifier_call所指向)执行的优先级,数字越小优先级越高,越会先被执行。我们看到这个通知块的结构并不复杂,甚至可以说是已经非常简单明了,每一个这样的通知块串起来就是我们所说的通知链了。
   Linux内核提供了三类通知链:原子通知链、阻塞通知链和原始通知链,它们的主要区别就是在执行通知链上的回调函数时是否有安全保护措施。下面我们分别看一下这三类通知链:
    1、原子通知链(Atomic Notifier Chains)
   原子通知链的链表头定义如下:

点击(此处)折叠或打开

  1. struct atomic_notifier_head {
  2.     spinlock_t lock;
  3.     struct notifier_block *head;
  4. };
    我们可以看到原子通知链采用的是自旋锁,通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,而且不允许阻塞。

   2、可阻塞通知链(Blocking Notifier Chains)
   可阻塞的通知链有两种类型,一种用信号量实现回调函数的加锁,另一种是采用互斥锁和叫做“可睡眠的读拷贝更新机制”(Sleepable Read-Copy UpdateSleepable Read-Copy Update),链表头的定义分别如下:   

点击(此处)折叠或打开

  1. struct blocking_notifier_head {
  2.     struct rw_semaphore rwsem;
  3.     struct notifier_block *head;
  4. };

点击(此处)折叠或打开

  1. struct srcu_notifier_head {
  2.     struct mutex mutex;
  3.     struct srcu_struct srcu;
  4.     struct notifier_block *head;
  5. };
    可阻塞型的通知链运行在进程空间的上下文环境里。

   3、原始通知链(Raw Notifier Chains)
   顾名思义,没有任何安保措施,对链表的加锁和保护全部由调用者自己实现,定义如下:

点击(此处)折叠或打开

  1. struct raw_notifier_head {
  2.     struct notifier_block *head;
  3. };
    关于三大类通知链详细的描述在notifier.h文件头部已经有非常详细的描述和说明了,这里我就浪费笔墨了,大家看源代码里的文注释完全足够了

   这三类通知链,我们该怎么用这才是我需要关心的问题。在定义自己的通知链的时候,心里必须明确,自己需要一个什么样类型的通知链,是原子的、可阻塞的还是一个原始通知链。内核中用于定义并初始化不同类通知链的函数分别是:

点击(此处)折叠或打开

  1. ATOMIC_NOTIFIER_HEAD(name)     //定义并初始化一个名为name的原子通知链
  2. BLOCKING_NOTIFIER_HEAD(name)   //定义并初始化一个名为name的阻塞通知链
  3. RAW_NOTIFIER_HEAD(name)        //定义并初始化一个名为name的原始通知链
    其实ATOMIC_NOTIFIER_HEAD(mynotifierlist)和下面的代码是等价的,展开之后如下

点击(此处)折叠或打开

  1. struct atomic_notifier_head mynotifierlist = 
  2. {
  3.     .lock = __SPIN_LOCK_UNLOCKED(mynotifierlist.lock),
  4.     .head = NULL 
  5. }

    另外两个接口也类似。如果我们已经有一个通知链的对象,Linux还提供了一组用于初始化一个通知链对象的API:

点击(此处)折叠或打开

  1. ATOMIC_INIT_NOTIFIER_HEAD(name)
  2. BLOCKING_INIT_NOTIFIER_HEAD(name)
  3. RAW_INIT_NOTIFIER_HEAD(name)

    这一组接口一般在下列格式的代码里见到的会比较多一点:

点击(此处)折叠或打开

  1. static struct atomic_notifier_head dock_notifier_list;
  2. ATOMIC_INIT_NOTIFIER_HEAD(&dock_notifier_list);
    
   OK,有了通知链只是第一步,接下来我们还需要提供往通知链上注册通知块、卸载通知块、已经遍历执行通知链上每个通知块里回调函数的基本接口,说白了就是单向链表的插入、删除和遍历,这样理解就可以了。
    内核提供最基本的通知链的常用接口如下:

点击(此处)折叠或打开

  1. static int notifier_chain_register(struct notifier_block **nl,  struct notifier_block *n);
  2. static int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n);
  3. static int __kprobes notifier_call_chain(struct notifier_block **nl, unsigned long val, void *v, int nr_to_call, int *nr_calls);

    这最基本的三个接口分别实现了通知链上通知块的注册、卸载和遍历操作,可以想象,原子通知链、可阻塞通知链和原始通知链一定会对基本通知链的操作函数进行一次包装的,事实也确实如此:

点击(此处)折叠或打开

  1. //原子通知链
  2. int atomic_notifier_chain_register(struct atomic_notifier_head *nh,  struct notifier_block *nb);
  3. int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh, struct notifier_block *nb);
  4. int atomic_notifier_call_chain(struct atomic_notifier_head *nh, unsigned long val, void *v);

  5. //可阻塞通知链
  6. int blocking_notifier_chain_register(struct blocking_notifier_head *nh, struct notifier_block *nb);
  7. int blocking_notifier_chain_cond_register(struct blocking_notifier_head *nh, struct notifier_block *nb);
  8. int srcu_notifier_chain_register(struct srcu_notifier_head *nh, struct notifier_block *nb);

  9. int blocking_notifier_call_chain(struct blocking_notifier_head *nh,unsigned long val, void *v);
  10. int srcu_notifier_call_chain(struct srcu_notifier_head *nh, unsigned long val, void *v);

  11. int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh, struct notifier_block *nb);
  12. int srcu_notifier_chain_unregister(struct srcu_notifier_head *nh, struct notifier_block *nb);

  13. //原始通知链
  14. int raw_notifier_chain_register(struct raw_notifier_head *nh, struct notifier_block *nb);
  15. int raw_notifier_chain_unregister(struct raw_notifier_head *nh,struct notifier_block *nb);
  16. int raw_notifier_call_chain(struct raw_notifier_head *nh, unsigned long val, void *v);

    上述这三类通知链的基本API又构成了内核中其他子系统定义、操作自己通知链的基础。例如,Netlink定义了一个原子通知链,所以,它对原子通知链的基本API又封装了一层,以形成自己的特色

点击(此处)折叠或打开

  1. /*net/netlink/af_netlink.c*/
  2. ...
  3. static ATOMIC_NOTIFIER_HEAD(netlink_chain);
  4. ...
  5. int netlink_register_notifier(struct notifier_block *nb)
  6. {
  7.     return atomic_notifier_chain_register(&netlink_chain, nb);
  8. }
  9. ...

  10. int netlink_unregister_notifier(struct notifier_block *nb)
  11. {
  12.     return atomic_notifier_chain_unregister(&netlink_chain, nb);
  13. }
  14. ...

    网络事件也有一个原子通知链:

点击(此处)折叠或打开

  1. /*net/core/netevent.c*/
  2. /*
  3.  *    Network event notifiers
  4.  *
  5.  *    Authors:
  6.  * Tom Tucker <tom@opengridcomputing.com>
  7.  * Steve Wise <swise@opengridcomputing.com>
  8.  *
  9.  *    This program is free software; you can redistribute it and/or
  10.  * modify it under the terms of the GNU General Public License
  11.  * as published by the Free Software Foundation; either version
  12.  * 2 of the License, or (at your option) any later version.
  13.  *
  14.  *    Fixes:
  15.  */

  16. #include <linux/rtnetlink.h>
  17. #include <linux/notifier.h>
  18. #include <net/netevent.h>

  19. static ATOMIC_NOTIFIER_HEAD(netevent_notif_chain);

  20. /**
  21.  *    register_netevent_notifier - register a netevent notifier block
  22.  *    @nb: notifier
  23.  *
  24.  *    Register a notifier to be called when a netevent occurs.
  25.  *    The notifier passed is linked into the kernel structures and must
  26.  *    not be reused until it has been unregistered. A negative errno code
  27.  *    is returned on a failure.
  28.  */
  29. int register_netevent_notifier(struct notifier_block *nb)
  30. {
  31.     int err;

  32.     err = atomic_notifier_chain_register(&netevent_notif_chain, nb);
  33.     return err;
  34. }

  35. /**
  36.  *    netevent_unregister_notifier - unregister a netevent notifier block
  37.  *    @nb: notifier
  38.  *
  39.  *    Unregister a notifier previously registered by
  40.  *    register_neigh_notifier(). The notifier is unlinked into the
  41.  *    kernel structures and may then be reused. A negative errno code
  42.  *    is returned on a failure.
  43.  */

  44. int unregister_netevent_notifier(struct notifier_block *nb)
  45. {
  46.     return atomic_notifier_chain_unregister(&netevent_notif_chain, nb);
  47. }

  48. /**
  49.  *    call_netevent_notifiers - call all netevent notifier blocks
  50.  * @val: value passed unmodified to notifier function
  51.  * @v: pointer passed unmodified to notifier function
  52.  *
  53.  *    Call all neighbour notifier blocks. Parameters and return value
  54.  *    are as for notifier_call_chain().
  55.  */

  56. int call_netevent_notifiers(unsigned long val, void *v)
  57. {
  58.     return atomic_notifier_call_chain(&netevent_notif_chain, val, v);
  59. }

  60. EXPORT_SYMBOL_GPL(register_netevent_notifier);
  61. EXPORT_SYMBOL_GPL(unregister_netevent_notifier);
  62. EXPORT_SYMBOL_GPL(call_netevent_notifiers)
   可阻塞通知链里的SRCU通知链,由于使用条件较苛刻,限制条件较多,所以使用的机会不是很多,除非你特别清楚这种类型的通知链的适用场合,在2.6.32的内核里只有cpufreq.c在用这种类型的通知链。
   未完,待续...
上一篇:关于openssl几个API的一点小收获
下一篇:Linux 内核通知链随笔【中】

文章评论