• Linux内核基础--事件通知链(notifier chain)



    http://blog.csdn.net/wuhzossibility/article/details/8079025


    1.1. 概述

           Linux内核中各个子系统相互依赖,当其中某个子系统状态发生改变时,就必须使用一定的机制告知使用其服务的其他子系统,以便其他子系统采取相应的措施。为满足这样的需求,内核实现了事件通知链机制(notificationchain)。

           通知链只能用在各个子系统之间,而不能在内核和用户空间进行事件的通知。组成内核的核心系统代码均位于kernel目录下,通知链表位于kernel/notifier.c中,对应的头文件为include/linux/notifier.h。通知链表机制并不复杂,实现它的代码只有区区几百行。

           事件通知链表是一个事件处理函数的列表,每个通知链都与某个或某些事件有关,当特定的事件发生时,就调用相应的事件通知链中的回调函数,进行相应的处理。


    图 1  内核通知链

    1.2.数据结构

    如图 1中所示,Linux的网络子系统一共有3个通知链:表示ipv4地址发生变化时的inetaddr_chain;表示ipv6地址发生变化的inet6addr_chain;还有表示设备注册、状态变化的netdev_chain。

    在这些链中都是一个个notifier_block结构: 

    1. struct notifier_block {  
    2.        int (*notifier_call)(struct notifier_block *, unsigned longvoid *);  
    3.        struct notifier_block *next;  
    4.        int priority;  
    5. };  

    其中,

    1.     notifier_call:当相应事件发生时应该调用的函数,由被通知方提供,如other_subsys_1;

    2.     notifier_block *next:用于链接成链表的指针;

    3.     priority:回调函数的优先级,一般默认为0。

    内核代码中一般把通知链命名为xxx_chain, xxx_nofitier_chain这种形式的变量名。围绕核心数据结构notifier_block,内核定义了四种通知链类型:

    1.  原子通知链( Atomic notifier chains ):通知链元素的回调函数(当事件发生时要执行的函数)在中断或原子操作上下文中运行,不允许阻塞。对应的链表头结构:

    1. struct atomic_notifier_head {  
    2.         spinlock_t  lock;  
    3.         struct  notifier_block *head;  
    4. };  

    2.  可阻塞通知链( Blocking notifier chains ):通知链元素的回调函数在进程上下文中运行,允许阻塞。对应的链表头:

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

    3.     原始通知链( Raw notifierchains ):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。对应的链表头:

    网络子系统就是该类型,通过以下宏实现head的初始化

    1. static RAW_NOTIFIER_HEAD(netdev_chain);  
    2. #define RAW_NOTIFIER_INIT(name)     {        
    3.               .head= NULL }  
    4. #define RAW_NOTIFIER_HEAD(name)               //调用他就好了  
    5. struct raw_notifier_head name =           
    6.                      RAW_NOTIFIER_INIT(name)     
    7. 即:  
    8. struct raw_notifier_head netdev_chain = {  
    9.           .head = NULL;  
    10. }  

    而其回调函数的注册,比如向netdev_chain的注册函数:register_netdevice_notifier。

    1. struct  raw_notifier_head {  
    2.         struct  notifier_block   *head;  
    3. };  
    4.    

    4.  SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体。对应的链表头:

    1. struct  srcu_notifier_head {  
    2.         struct  mutex mutex;  
    3.         struct  srcu_struct  srcu;  
    4.         struct  notifier_block  *head;  
    5. };  

    1.3.  运行机理

    被通知一方(other_subsys_x)通过notifier_chain_register向特定的chain注册回调函数,并且一般而言特定的子系统会用特定的notifier_chain_register包装函数来注册,比如路由子系统使用的是网络子系统的:register_netdevice_notifier来注册他的notifier_block。

    1.3.1.  向事件通知链注册的步骤

    1. 申明struct notifier_block结构

    2. 编写notifier_call函数

    3. 调用特定的事件通知链的注册函数,将notifier_block注册到通知链中

    如果内核组件需要处理够某个事件通知链上发出的事件通知,其就该在初始化时在该通知链上注册回调函数。

    1.3.2.  通知子系统有事件发生

    inet_subsys是通过notifier_call_chain来通知其他的子系统(other_subsys_x)的。

    notifier_call_chain会按照通知链上各成员的优先级顺序执行回调函数(notifier_call_x);回调函数的执行现场在notifier_call_chain进程地址空间;其返回值是NOTIFY_XXX的形式,在include/linux/notifier.h中:

    1. #define NOTIFY_DONE            0x0000         /* 对事件视而不见 */  
    2. #define NOTIFY_OK          0x0001         /* 事件正确处理 */  
    3. #define NOTIFY_STOP_MASK  0x8000         /*由notifier_call_chain检查,看继续调用回调函数,还是停止,_BAD和_STOP中包含该标志 */  
    4. #define NOTIFY_BAD        (NOTIFY_STOP_MASK|0x0002)       /*事件处理出错,不再继续调用回调函数 */  
    5. /* 
    6.  *Clean way to return from the notifier and stop further calls. 
    7.  */  
    8. #define NOTIFY_STOP             (NOTIFY_OK|NOTIFY_STOP_MASK)    /*  回调出错,不再继续调用该事件回调函数 */  

    notifier_call_chain捕获并返回最后一个事件处理函数的返回值;注意:notifier_call_chain可能同时被不同的cpu调用,故而调用者必须保证互斥。

    1.3.3.  事件列表

           对于网络子系统而言,其事件常以NETDEV_XXX命名;描述了网络设备状态(dev->flags)、传送队列状态(dev->state)、设备注册状态(dev->reg_state),以及设备的硬件功能特性(dev->features):

    include/linux/notifier.h中

    1. /* netdevice notifier chain */  
    2. #define NETDEV_UP  0x0001  /* 激活一个网络设备 */  
    3. #define NETDEV_DOWN  0x0002f /* 停止一个网络设备,所有对该设备的引用都应释放 */  
    4. #define NETDEV_REBOOT       0x0003       /* 检查到网络设备接口硬件崩溃,硬件重启 */  
    5. #define NETDEV_CHANGE       0x0004  /* 网络设备的数据包队列状态发生改变 */  
    6. #define NETDEV_REGISTER  0x0005   /*一个网络设备事例注册到系统中,但尚未激活 */  
    7. #define NETDEV_UNREGISTER       0x0006       /*网络设备驱动已卸载 */  
    8. #define NETDEV_CHANGEMTU      0x0007  /*MTU发生了改变 */  
    9. #define NETDEV_CHANGEADDR    0x0008  /*硬件地址发生了改变 */  
    10. #define NETDEV_GOING_DOWN   0x0009  /*网络设备即将注销,有dev->close报告,通知相关子系统处理 */  
    11. #define NETDEV_CHANGENAME   0x000A  /*网络设备名改变 */  
    12. #define NETDEV_FEAT_CHANGE    0x000B  /*feature网络硬件功能改变 */  
    13. #define NETDEV_BONDING_FAILOVER 0x000C  /*    */  
    14. #define NETDEV_PRE_UP        0x000D  /*    */  
    15. #define NETDEV_BONDING_OLDTYPE  0x000E              /*    */  
    16. #define NETDEV_BONDING_NEWTYPE  0x000F      /*    */  


    1.4.  简单一例:

           通过上面所述,notifier_chain机制只能在内核个子系统间使用,因此,这里使用3个模块:test_notifier_chain_0、test_notifier_chain_1、test_notifier_chain_2;当 test_notifier_chain_2通过module_init初始化模块时发出事件TESTCHAIN_2_INIT;然后 test_notifier_chain_1作出相应的处理:打印 test_notifier_chain_2正在初始化。

    1. /* test_chain_0.c :0. 申明一个通知链;1. 向内核注册通知链;2. 定义事件; 3. 导出符号,因而必需最后退出*/  
    2.   
    3. #include <linux/notifier.h>  
    4. #include <linux/module.h>  
    5. #include <linux/init.h>  
    6. #include <linux/kernel.h> /* printk() */  
    7. #include <linux/fs.h> /* everything() */  
    8.   
    9. #define TESTCHAIN_INIT 0x52U  
    10. static RAW_NOTIFIER_HEAD(test_chain);  
    11.   
    12. /* define our own notifier_call_chain */  
    13. static int call_test_notifiers(unsigned long val, void *v)  
    14. {  
    15.     return raw_notifier_call_chain(&test_chain, val, v);  
    16. }  
    17. EXPORT_SYMBOL(call_test_notifiers);  
    18.   
    19. /* define our own notifier_chain_register func */  
    20.  static int register_test_notifier(struct notifier_block *nb)  
    21. {  
    22.     int err;  
    23.     err = raw_notifier_chain_register(&test_chain, nb);  
    24.   
    25.     if(err)  
    26.         goto out;  
    27.   
    28. out:  
    29.     return err;  
    30. }  
    31.   
    32. EXPORT_SYMBOL(register_test_notifier);  
    33.   
    34. static int __init test_chain_0_init(void)  
    35. {  
    36.     printk(KERN_DEBUG "I'm in test_chain_0 ");  
    37.   
    38.     return 0;  
    39. }  
    40.   
    41. static void __exit test_chain_0_exit(void)  
    42. {  
    43.     printk(KERN_DEBUG "Goodbye to test_chain_0 ");  
    44. //  call_test_notifiers(TESTCHAIN_EXIT, (int *)NULL);  
    45. }  
    46.   
    47. MODULE_LICENSE("GPL v2");  
    48. MODULE_AUTHOR("fishOnFly");  
    49.   
    50. module_init(test_chain_0_init);  
    51. module_exit(test_chain_0_exit);  
    52.   
    53. /* test_chain_1.c :1. 定义回调函数;2. 定义notifier_block;3. 向chain_0注册notifier_block;*/  
    54. #include <linux/notifier.h>  
    55. #include <linux/module.h>  
    56. #include <linux/init.h>  
    57.   
    58. #include <linux/kernel.h> /* printk() */  
    59. #include <linux/fs.h> /* everything() */  
    60.   
    61. extern int register_test_notifier(struct notifier_block *nb);  
    62. #define TESTCHAIN_INIT 0x52U  
    63.   
    64. /* realize the notifier_call func */  
    65. int test_init_event(struct notifier_block *nb, unsigned long event,  
    66.     void *v)  
    67. {  
    68.     switch(event){  
    69.     case TESTCHAIN_INIT:  
    70.         printk(KERN_DEBUG "I got the chain event: test_chain_2 is on the way of init ");  
    71.         break;  
    72.   
    73.     default:  
    74.         break;  
    75.     }  
    76.   
    77.     return NOTIFY_DONE;  
    78. }  
    79. /* define a notifier_block */  
    80. static struct notifier_block test_init_notifier = {  
    81.     .notifier_call = test_init_event,  
    82. };  
    83. static int __init test_chain_1_init(void)  
    84. {  
    85.     printk(KERN_DEBUG "I'm in test_chain_1 ");  
    86.     register_test_notifier(&test_init_notifier);<span style="white-space:pre">  </span>// 由chain_0提供的设施  
    87.     return 0;  
    88. }  
    89.   
    90. static void __exit test_chain_1_exit(void)  
    91. {  
    92.     printk(KERN_DEBUG "Goodbye to test_clain_l ");  
    93. }  
    94.   
    95. MODULE_LICENSE("GPL");  
    96. MODULE_AUTHOR("fishOnFly");  
    97.   
    98. module_init(test_chain_1_init);  
    99. module_exit(test_chain_1_exit);  
    100.   
    101. /* test_chain_2.c:发出通知链事件*/  
    102.   
    103. #include <linux/notifier.h>  
    104. #include <linux/module.h>  
    105. #include <linux/init.h>  
    106. #include <linux/kernel.h> /* printk() */  
    107. #include <linux/fs.h> /* everything() */  
    108.   
    109. extern int call_test_notifiers(unsigned long val, void *v);  
    110. #define TESTCHAIN_INIT 0x52U  
    111.   
    112. static int __init test_chain_2_init(void)  
    113. {  
    114.     printk(KERN_DEBUG "I'm in test_chain_2 ");  
    115.     call_test_notifiers(TESTCHAIN_INIT, "no_use");  
    116.       
    117.     return 0;  
    118. }  
    119.   
    120. static void __exit test_chain_2_exit(void)  
    121. {  
    122.     printk(KERN_DEBUG "Goodbye to test_chain_2 ");  
    123. }  
    124.   
    125. MODULE_LICENSE("GPL v2");  
    126. MODULE_AUTHOR("fishOnFly");  
    127.   
    128. module_init(test_chain_2_init);  
    129. module_exit(test_chain_2_exit);  
    130.   
    131. # Makefile  
    132.   
    133. # Comment/uncomment the following line to disable/enable debugging  
    134. # DEBUG = y  
    135.   
    136.   
    137. # Add your debugging flag (or not) to CFLAGS  
    138. ifeq ($(DEBUG),y)  
    139.   DEBFLAGS = -O -g -DSCULL_DEBUG # "-O" is needed to expand inlines  
    140. else  
    141.   DEBFLAGS = -O2  
    142. endif  
    143.   
    144.   
    145. ifneq ($(KERNELRELEASE),)  
    146. # call from kernel build system  
    147.   
    148. obj-m   := test_chain_0.o test_chain_1.o test_chain_2.o  
    149.   
    150. else  
    151.   
    152. KERNELDIR ?= /lib/modules/$(shell uname -r)/build  
    153. PWD       := $(shell pwd)  
    154.   
    155. modules:  
    156.     $(MAKE) -C $(KERNELDIR) M=$(PWD) modules  
    157.   
    158. endif  
    159.   
    160.   
    161.   
    162. clean:  
    163.     rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions  
    164.   
    165. depend .depend dep:  
    166.     $(CC) $(CFLAGS) -M *.c > .depend  
    167.   
    168.   
    169. ifeq (.depend,$(wildcard .depend))  
    170. include .depend  
    171. endif  
    172.   
    173. [wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_0.ko  
    174. [wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_1.ko  
    175. [wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_2.ko  
    176.    
    177.   
    178. [wang2@iwooing: notifier_chian]$ dmesg  
    179.   
    180. [ 5950.112649] I'm in test_chain_0  
    181. [ 5956.766610] I'm in test_chain_1  
    182. [ 5962.570003] I'm in test_chain_2  
    183. [ 5962.570008] I got the chain event: test_chain_2 is on the way of init  
    184.   
    185. [ 6464.042975] Goodbye to test_chain_2  
    186. [ 6466.368030] Goodbye to test_clain_l  
    187. [ 6468.371479] Goodbye to test_chain_0  


    1. <p></p>  

  • 相关阅读:
    BloomFilter——读数学之美札记
    线性时间求最长回文子串
    python之装饰器详解
    多线程快速排序(思考)
    搬家
    webapp用户身份认证方案 JSON WEB TOKEN 实现
    Java多线程问题总结
    synchronized的4种用法
    线程池的工作原理及使用示例
    Http 请求 GET和POST的区别
  • 原文地址:https://www.cnblogs.com/ztguang/p/12645181.html
Copyright © 2020-2023  润新知