设为首页 收藏本站
查看: 246|回复: 0

[经验分享] Linux 内核通知链随笔【上】

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

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



    /* include/linux/notifier.h*/
    struct notifier_block {
        int (*notifier_call)(struct notifier_block *, unsigned long, void *);
        struct notifier_block *next;
        int priority;
    };


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



    struct atomic_notifier_head {
        spinlock_t lock;
        struct notifier_block *head;
    };

    我们可以看到原子通知链采用的是自旋锁,通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,而且不允许阻塞。

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



    struct blocking_notifier_head {
        struct rw_semaphore rwsem;
        struct notifier_block *head;
    };



    struct srcu_notifier_head {
        struct mutex mutex;
        struct srcu_struct srcu;
        struct notifier_block *head;
    };

    可阻塞型的通知链运行在进程空间的上下文环境里。

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



    struct raw_notifier_head {
        struct notifier_block *head;
    };

    关于三大类通知链详细的描述在notifier.h文件头部已经有非常详细的描述和说明了,这里我就浪费笔墨了,大家看源代码里的英文注释完全足够了。

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



    ATOMIC_NOTIFIER_HEAD(name)     //定义并初始化一个名为name的原子通知链
    BLOCKING_NOTIFIER_HEAD(name)   //定义并初始化一个名为name的阻塞通知链
    RAW_NOTIFIER_HEAD(name)        //定义并初始化一个名为name的原始通知链

    其实ATOMIC_NOTIFIER_HEAD(mynotifierlist)和下面的代码是等价的,展开之后如下:



    struct atomic_notifier_head mynotifierlist =
    {
        .lock = __SPIN_LOCK_UNLOCKED(mynotifierlist.lock),
        .head = NULL
    }


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



    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);

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



    static int notifier_chain_register(struct notifier_block **nl,  struct notifier_block *n);
    static int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n);
    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_chain_register(struct atomic_notifier_head *nh,  struct notifier_block *nb);
    int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh, struct notifier_block *nb);
    int atomic_notifier_call_chain(struct atomic_notifier_head *nh, unsigned long val, void *v);

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

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

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

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


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



    /*net/netlink/af_netlink.c*/
    ...
    static ATOMIC_NOTIFIER_HEAD(netlink_chain);
    ...
    int netlink_register_notifier(struct notifier_block *nb)
    {
        return atomic_notifier_chain_register(&netlink_chain, nb);
    }
    ...

    int netlink_unregister_notifier(struct notifier_block *nb)
    {
        return atomic_notifier_chain_unregister(&netlink_chain, nb);
    }
    ...


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



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

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

    static ATOMIC_NOTIFIER_HEAD(netevent_notif_chain);

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

        err = atomic_notifier_chain_register(&netevent_notif_chain, nb);
        return err;
    }

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

    int unregister_netevent_notifier(struct notifier_block *nb)
    {
        return atomic_notifier_chain_unregister(&netevent_notif_chain, nb);
    }

    /**
     *    call_netevent_notifiers - call all netevent notifier blocks
     * @val: value passed unmodified to notifier function
     * @v: pointer passed unmodified to notifier function
     *
     *    Call all neighbour notifier blocks. Parameters and return value
     *    are as for notifier_call_chain().
     */

    int call_netevent_notifiers(unsigned long val, void *v)
    {
        return atomic_notifier_call_chain(&netevent_notif_chain, val, v);
    }

    EXPORT_SYMBOL_GPL(register_netevent_notifier);
    EXPORT_SYMBOL_GPL(unregister_netevent_notifier);
    EXPORT_SYMBOL_GPL(call_netevent_notifiers)

   可阻塞通知链里的SRCU通知链,由于使用条件较苛刻,限制条件较多,所以使用的机会不是很多,除非你特别清楚这种类型的通知链的适用场合,在2.6.32的内核里只有cpufreq.c在用这种类型的通知链。
   未完,待续...

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-23742-1-1.html 上篇帖子: linux实用技巧:使用快照制作虚拟机 下篇帖子: Linux 内核通知链随笔【中】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表