日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux内核通知链机制的原理及实现【转】

發(fā)布時間:2025/5/22 linux 47 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux内核通知链机制的原理及实现【转】 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

轉(zhuǎn)自:http://www.cnblogs.com/armlinux/archive/2011/11/11/2396781.html

一、概念:

??? 大多數(shù)內(nèi)核子系統(tǒng)都是相互獨立的,因此某個子系統(tǒng)可能對其它子系統(tǒng)產(chǎn)生的事件感興趣。為了滿足這個需求,也即是讓某個子系統(tǒng)在發(fā)生某個事件時通知其它的子系統(tǒng),Linux內(nèi)核提供了通知鏈的機制。通知鏈表只能夠在內(nèi)核的子系統(tǒng)之間使用,而不能夠在內(nèi)核與用戶空間之間進行事件的通知。 通知鏈表是一個函數(shù)鏈表,鏈表上的每一個節(jié)點都注冊了一個函數(shù)。當(dāng)某個事情發(fā)生時,鏈表上所有節(jié)點對應(yīng)的函數(shù)就會被執(zhí)行。所以對于通知鏈表來說有一個通知方與一個接收方。在通知這個事件時所運行的函數(shù)由被通知方?jīng)Q定,實際上也即是被通知方注冊了某個函數(shù),在發(fā)生某個事件時這些函數(shù)就得到執(zhí)行。其實和系統(tǒng)調(diào)用signal的思想差不多。

二、數(shù)據(jù)結(jié)構(gòu):

通知鏈有四種類型:

  • 原子通知鏈(?Atomic?notifier?chains?):通知鏈元素的回調(diào)函數(shù)(當(dāng)事件發(fā)生時要執(zhí)行的函數(shù))只能在中斷上下文中運行,不允許阻塞。對應(yīng)的鏈表頭結(jié)構(gòu):

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

  • 可阻塞通知鏈(?Blocking?notifier?chains?):通知鏈元素的回調(diào)函數(shù)在進程上下文中運行,允許阻塞。對應(yīng)的鏈表頭:

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

  • 原始通知鏈(?Raw?notifier?chains?):對通知鏈元素的回調(diào)函數(shù)沒有任何限制,所有鎖和保護機制都由調(diào)用者維護。對應(yīng)的鏈表頭:

    struct?raw_notifier_head?
    {
    ????struct?notifier_block?*head;
    };

  • SRCU?通知鏈(?SRCU?notifier?chains?):可阻塞通知鏈的一種變體。對應(yīng)的鏈表頭:

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

  • 通知鏈的核心結(jié)構(gòu):

    struct?notifier_block?
    {
    ????int?(*notifier_call)(struct?notifier_block?*,?unsigned?long,?void?*);
    ????struct?notifier_block?*next;
    ????int?priority;
    };

    ?

    其中notifier_call是通知鏈要執(zhí)行的函數(shù)指針,next用來連接其它的通知結(jié)構(gòu),priority是這個通知的優(yōu)先級,同一條鏈上的notifier_block{}是按優(yōu)先級排列的。內(nèi)核代碼中一般把通知鏈命名為xxx_chain,?xxx_nofitier_chain這種形式的變量名。

    三、運作機制:

    通知鏈的運作機制包括兩個角色:

  • 被通知者:對某一事件感興趣一方。定義了當(dāng)事件發(fā)生時,相應(yīng)的處理函數(shù),即回調(diào)函數(shù)。但需要事先將其注冊到通知鏈中(被通知者注冊的動作就是在通知鏈中增加一項)。
  • 通知者:事件的通知者。當(dāng)檢測到某事件,或者本身產(chǎn)生事件時,通知所有對該事件感興趣的一方事件發(fā)生。他定義了一個通知鏈,其中保存了每一個被通知者對事件的處理函數(shù)(回調(diào)函數(shù))。通知這個過程實際上就是遍歷通知鏈中的每一項,然后調(diào)用相應(yīng)的事件處理函數(shù)。
  • 包括以下過程:

  • 通知者定義通知鏈。
  • 被通知者向通知鏈中注冊回調(diào)函數(shù)。
  • 當(dāng)事件發(fā)生時,通知者發(fā)出通知(執(zhí)行通知鏈中所有元素的回調(diào)函數(shù))。
  • 被通知者調(diào)用?notifier_chain_register?函數(shù)注冊回調(diào)函數(shù),該函數(shù)按照優(yōu)先級將回調(diào)函數(shù)加入到通知鏈中:

    static?int?notifier_chain_register(struct?notifier_block?**nl,?struct?notifier_block?*n)
    {
    ????while?((*nl)?!=?NULL)?
    ????{
    ????????if?(n->priority?>?(*nl)->priority)
    ????????break;
    ????????nl?=?&((*nl)->next);
    ????}
    ????
    ????n->next?=?*nl;
    ????rcu_assign_pointer(*nl,?n);
    ????
    ????return?0;
    }

    ?

    注銷回調(diào)函數(shù)則使用?notifier_chain_unregister?函數(shù),即將回調(diào)函數(shù)從通知鏈中刪除:

    static?int?notifier_chain_unregister(struct?notifier_block?**nl,?struct?notifier_block?*n)
    {
    ????while?((*nl)?!=?NULL)?
    ????{
    ????????if?((*nl)?==?n)?
    ????????{
    ????????????rcu_assign_pointer(*nl,?n->next);
    ????????
    ????????????return?0;
    ????????}
    ????
    ????????nl?=?&((*nl)->next);
    ????}
    ????
    ????return?-ENOENT;
    }

    ?

    通知者調(diào)用?notifier_call_chain?函數(shù)通知事件的到達,這個函數(shù)會遍歷通知鏈中所有的元素,然后依次調(diào)用每一個的回調(diào)函數(shù)(即完成通知動作):

    static?int?__kprobes notifier_call_chain(struct?notifier_block?**nl,?unsigned?long?val,?void?*v,?int?nr_to_call,?int?*nr_calls)
    {
    ????int?ret?=?NOTIFY_DONE;
    ????struct?notifier_block?*nb,?*next_nb;
    ????
    ????nb?=?rcu_dereference(*nl);
    ????
    ????while?(nb?&&?nr_to_call)?
    ????{
    ????????next_nb?=?rcu_dereference(nb->next);
    ????
    #ifdef?CONFIG_DEBUG_NOTIFIERS
    ????????if?(unlikely(!func_ptr_is_kernel_text(nb->notifier_call)))?
    ????????{
    ????????????WARN(1,?"Invalid notifier called!");
    ????????????
    ????????????nb?=?next_nb;
    ????????????
    ????????????continue;
    ????????}
    #endif

    ????????ret?=?nb->notifier_call(nb,?val,?v);
    ????????
    ????????if?(nr_calls)
    ????????
    ????????(*nr_calls)++;
    ????????
    ????????if?((ret?&?NOTIFY_STOP_MASK)?==?NOTIFY_STOP_MASK)
    ????????
    ????????break;
    ????????
    ????????nb?=?next_nb;
    ????????
    ????????nr_to_call--;
    ????}
    ????
    ????return?ret;
    }

    ?

    ??? 參數(shù)nl是通知鏈的頭部,val表示事件類型,v用來指向通知鏈上的函數(shù)執(zhí)行時需要用到的參數(shù),一般不同的通知鏈,參數(shù)類型也不一樣,例如當(dāng)通知一個網(wǎng)卡被注冊時,v就指向net_device結(jié)構(gòu),nr_to_call表示準(zhǔn)備最多通知幾個,-1表示整條鏈都通知,nr_calls非空的話,返回通知了多少個。

    ????每個被執(zhí)行的notifier_block回調(diào)函數(shù)的返回值可能取值為以下幾個:

  • NOTIFY_DONE:表示對相關(guān)的事件類型不關(guān)心。
  • NOTIFY_OK:順利執(zhí)行。
  • NOTIFY_BAD:執(zhí)行有錯。
  • NOTIFY_STOP:停止執(zhí)行后面的回調(diào)函數(shù)。
  • NOTIFY_STOP_MASK:停止執(zhí)行的掩碼。
  • ??? Notifier_call_chain()把最后一個被調(diào)用的回調(diào)函數(shù)的返回值作為它的返回值。

    四、舉例應(yīng)用:

    在這里,寫了一個簡單的通知鏈表的代碼。實際上,整個通知鏈的編寫也就兩個過程:

  • 首先是定義自己的通知鏈的頭節(jié)點,并將要執(zhí)行的函數(shù)注冊到自己的通知鏈中。
  • 其次則是由另外的子系統(tǒng)來通知這個鏈,讓其上面注冊的函數(shù)運行。
  • ????? 這里將第一個過程分成了兩步來寫,第一步是定義了頭節(jié)點和一些自定義的注冊函數(shù)(針對該頭節(jié)點的),第二步則是使用自定義的注冊函數(shù)注冊了一些通知鏈節(jié)點。分別在代碼buildchain.c與regchain.c中。發(fā)送通知信息的代碼為notify.c。

    代碼1?buildchain.c。它的作用是自定義一個通知鏈表test_chain,然后再自定義兩個函數(shù)分別向這個通知鏈中加入或刪除節(jié)點,最后再定義一個函數(shù)通知這個test_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>
    MODULE_LICENSE("GPL");

    /*
    * 定義自己的通知鏈頭結(jié)點以及注冊和卸載通知鏈的外包函數(shù)
    */

    /*
    * RAW_NOTIFIER_HEAD是定義一個通知鏈的頭部結(jié)點,
    * 通過這個頭部結(jié)點可以找到這個鏈中的其它所有的notifier_block
    */
    static?RAW_NOTIFIER_HEAD(test_chain);

    /*
    * 自定義的注冊函數(shù),將notifier_block節(jié)點加到剛剛定義的test_chain這個鏈表中來
    * raw_notifier_chain_register會調(diào)用notifier_chain_register
    */
    int?register_test_notifier(struct?notifier_block?*nb)
    {
    ??return?raw_notifier_chain_register(&test_chain,?nb);
    }
    EXPORT_SYMBOL(register_test_notifier);

    int?unregister_test_notifier(struct?notifier_block?*nb)
    {
    ??return?raw_notifier_chain_unregister(&test_chain,?nb);
    }
    EXPORT_SYMBOL(unregister_test_notifier);

    /*
    * 自定義的通知鏈表的函數(shù),即通知test_chain指向的鏈表中的所有節(jié)點執(zhí)行相應(yīng)的函數(shù)
    */
    int?test_notifier_call_chain(unsigned?long?val,?void?*v)
    {
    ??return?raw_notifier_call_chain(&test_chain,?val,?v);
    }
    EXPORT_SYMBOL(test_notifier_call_chain);

    /*
    * init and exit?
    */
    static?int?__init init_notifier(void)
    {
    ??printk("init_notifier\n");
    ??return?0;
    }

    static?void?__exit exit_notifier(void)
    {
    ????printk("exit_notifier\n");
    }

    module_init(init_notifier);
    module_exit(exit_notifier);

    ?

    代碼2?regchain.c。該代碼的作用是將test_notifier1?test_notifier2?test_notifier3這三個節(jié)點加到之前定義的test_chain這個通知鏈表上,同時每個節(jié)點都注冊了一個函數(shù):

    #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>
    MODULE_LICENSE("GPL");

    /*
    * 注冊通知鏈
    */
    extern?int?register_test_notifier(struct?notifier_block*);
    extern?int?unregister_test_notifier(struct?notifier_block*);

    static?int?test_event1(struct?notifier_block?*this,?unsigned?long?event,?void?*ptr)
    {
    ??printk("In Event 1: Event Number is %d\n",?event);
    ??return?0;?
    }

    static?int?test_event2(struct?notifier_block?*this,?unsigned?long?event,?void?*ptr)
    {
    ??printk("In Event 2: Event Number is %d\n",?event);
    ??return?0;?
    }

    static?int?test_event3(struct?notifier_block?*this,?unsigned?long?event,?void?*ptr)
    {
    ??printk("In Event 3: Event Number is %d\n",?event);
    ??return?0;?
    }

    /*
    * 事件1,該節(jié)點執(zhí)行的函數(shù)為test_event1
    */
    static?struct?notifier_block test_notifier1?=
    {
    ????.notifier_call?=?test_event1,
    };

    /*
    * 事件2,該節(jié)點執(zhí)行的函數(shù)為test_event1
    */
    static?struct?notifier_block test_notifier2?=
    {
    ????.notifier_call?=?test_event2,
    };

    /*
    * 事件3,該節(jié)點執(zhí)行的函數(shù)為test_event1
    */
    static?struct?notifier_block test_notifier3?=
    {
    ????.notifier_call?=?test_event3,
    };

    /*
    * 對這些事件進行注冊
    */
    static?int?__init reg_notifier(void)
    {
    ??int?err;
    ??printk("Begin to register:\n");
    ??
    ??err?=?register_test_notifier(&test_notifier1);
    ??if?(err)
    ??{
    ????printk("register test_notifier1 error\n");
    ????return?-1;?
    ??}
    ??printk("register test_notifier1 completed\n");

    ??err?=?register_test_notifier(&test_notifier2);
    ??if?(err)
    ??{
    ????printk("register test_notifier2 error\n");
    ????return?-1;?
    ??}
    ??printk("register test_notifier2 completed\n");

    ??err?=?register_test_notifier(&test_notifier3);
    ??if?(err)
    ??{
    ????printk("register test_notifier3 error\n");
    ????return?-1;?
    ??}
    ??printk("register test_notifier3 completed\n");
    ??
    ??return?err;
    }

    /*
    * 卸載剛剛注冊了的通知鏈
    */
    static?void?__exit unreg_notifier(void)
    {
    ??printk("Begin to unregister\n");
    ??unregister_test_notifier(&test_notifier1);
    ??unregister_test_notifier(&test_notifier2);
    ??unregister_test_notifier(&test_notifier3);
    ??printk("Unregister finished\n");
    }

    module_init(reg_notifier);
    module_exit(unreg_notifier);

    ?

    代碼3?notify.c。該代碼的作用就是向test_chain通知鏈中發(fā)送消息,讓鏈中的函數(shù)運行:

    #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>
    MODULE_LICENSE("GPL");

    extern?int?test_notifier_call_chain(unsigned?long?val,?void?*v);

    /*
    * 向通知鏈發(fā)送消息以觸發(fā)注冊了的函數(shù)
    */
    static?int?__init call_notifier(void)
    {
    ??int?err;
    ??printk("Begin to notify:\n");

    ??/*
    ??* 調(diào)用自定義的函數(shù),向test_chain鏈發(fā)送消息
    ??*/
    ??printk("==============================\n");
    ??err?=?test_notifier_call_chain(1,?NULL);
    ??printk("==============================\n");
    ??if?(err)
    ??????????printk("notifier_call_chain error\n");
    ??return?err;
    }

    static?void?__exit uncall_notifier(void)
    {
    ????printk("End notify\n");
    }

    module_init(call_notifier);
    module_exit(uncall_notifier);

    ?

    Makefile文件:

    obj-m:=buildchain.o regchain.o notify.o
    CURRENT_PATH?:=?$(shell pwd)
    LINUX_KERNEL?:=?$(shell uname?-r)
    KERNELDIR?:=?/usr/src/linux-headers-$(LINUX_KERNEL)

    all:
    make?-C $(KERNELDIR)?M=$(CURRENT_PATH)?modules

    clean:

    make?-C $(KERNELDIR)?M=$(CURRENT_PATH)?clean?

    ?

    運行(注意insmod要root權(quán)限):

    make

    insmod buildchain.ko
    insmod regchain.ko
    insmod notify.ko

    ?

    這樣就可以看到通知鏈運行的效果了:

    init_notifier
    Begin to?register:
    register?test_notifier1 completed
    register?test_notifier2 completed
    register?test_notifier3 completed
    Begin to notify:
    ==============================
    In Event 1:?Event Number is 1
    In Event 2:?Event Number is 1
    In Event 3:?Event Number is 1
    ==============================

    分類:?Linux 【作者】張昺華 【出處】http://www.cnblogs.com/sky-heaven/ 【博客園】 http://www.cnblogs.com/sky-heaven/ 【新浪博客】 http://blog.sina.com.cn/u/2049150530 【知乎】 http://www.zhihu.com/people/zhang-bing-hua 【我的作品---旋轉(zhuǎn)倒立擺】 http://v.youku.com/v_show/id_XODM5NDAzNjQw.html?spm=a2hzp.8253869.0.0&from=y1.7-2 【我的作品---自平衡自動循跡車】 http://v.youku.com/v_show/id_XODM5MzYyNTIw.html?spm=a2hzp.8253869.0.0&from=y1.7-2 【新浪微博】 張昺華--sky 【twitter】 @sky2030_ 【facebook】 張昺華 zhangbinghua 本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利.

    總結(jié)

    以上是生活随笔為你收集整理的Linux内核通知链机制的原理及实现【转】的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。