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结构: 

 

[cpp] view plain copy

 

  1. struct notifier_block {  
  2.        int (*notifier_call)(struct notifier_block *, unsigned long, void *);  
  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 ):通知链元素的回调函数(当事件发生时要执行的函数)在中断或原子操作上下文中运行,不允许阻塞。对应的链表头结构:

 

[cpp] view plain copy

 

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

 

 

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

 

[cpp] view plain copy

 

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

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

 

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

 

[cpp] view plain copy

 

  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。

 

[cpp] view plain copy

 

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

 

 

4.  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  *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中:

 

[cpp] view plain copy

 

  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中

 

[cpp] view plain copy

 

  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正在初始化。

 

[cpp] view plain copy

 

  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\n");  
  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\n");  
  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\n");  
  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\n");  
  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\n");  
  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\n");  
  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\n");  
  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  

 

[cpp] view plain copy

 

    1. <p></p>  
时间: 2024-09-25 18:36:36

Linux内核基础--事件通知链(notifier chain)【转】的相关文章

Linux内核基础--事件通知链(notifier chain)good【转】

转自:http://www.cnblogs.com/pengdonglin137/p/4075148.html 阅读目录(Content) 1.1. 概述 1.2.数据结构  1.3.  运行机理 1.4.  简单一例:  一.概述 二.结构体 三.操作过程 四.实例 转载: http://blog.csdn.net/wuhzossibility/article/details/8079025 http://blog.chinaunix.net/uid-27717694-id-4286337.h

notifier chain — 内核通知链【转】

转自:http://blog.csdn.net/g_salamander/article/details/8081724 大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣.为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子系统,Linux内核提供了通知链的机制.通知链表只能够在内核的子系统之间使用,而不能够在内核与用户空间之间进行事件的通知.通知链表是一个函数链表,链表上的每一个节点都注册了一个函数.当某个事情发生时,链表上所有节点对应的函数就会被执行

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

转自:http://www.cnblogs.com/armlinux/archive/2011/11/11/2396781.html 一.概念:     大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣.为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子系统,Linux内核提供了通知链的机制.通知链表只能够在内核的子系统之间使用,而不能够在内核与用户空间之间进行事件的通知. 通知链表是一个函数链表,链表上的每一个节点都注册了一个函数.当某个事情发生时,

戴文的Linux内核专题:26 配置内核 (22)

戴文的Linux内核专题:26 配置内核 (22) 你好!本篇我们将继续配置"kernel hacks",接着我们会配置整个安全系统. Alpha和s390处理器需要配置下一个特性(Force weak per-cpu definitions).这个特性修复了一个在这类处理器中普遍存在的寻址问题.其他的处理器无需启用这个特性. 内核转储可以用这个特殊的调试工具测试(Linux Kernel Dump Test Tool Module).这个软件会允许内核开发者触发一个假错误来导致内核转

Linux内核调试技术——进程D状态死锁检测

Linux的进程存在多种状态,如TASK_RUNNING的运行态.EXIT_DEAD的停止态和TASK_INTERRUPTIBLE的接收信号的等待状态等等(可在include/linux/sched.h中查看).其中有一种状态等待为TASK_UNINTERRUPTIBLE,称为D状态,该种状态下进程不接收信号,只能通过wake_up唤醒.处于这种状态的情况有很多,例如mutex锁就可能会设置进程于该状态,有时候进程在等待某种IO资源就绪时(wait_event机制)会设置进程进入该状态.一般情况

行尸走肉:过时Linux内核的安全风险

设备年年新,内核永不换.早该被淘汰的Linux内核版本,依然阴魂不散地扎根在各种各样的设备中,驱动着这些设备如同<行尸走肉>的丧尸游荡在世界各地. Linux内核安全漏洞是新闻头条常客.最近又有一个隐身十年之久的严重内核漏洞被曝光了.但是,这到底代表着什么现实意义呢?为什么Linux内核安全很重要?老旧内核上的漏洞在众多设备中苟延残喘到底有什么影响? Linux是操作系统的王者 毋庸置疑,Linux系操作系统的流行度无人能出其右:3/4的IoT设备运行Linux:2/3的在线服务器用Linux

Linux 内核通知链随笔【中】【转】

转自:http://blog.chinaunix.net/uid-23069658-id-4364171.html 关于内核通知链不像Netlink那样,既可以用于内核与用户空间的通信,还能用于内核不同子系统之间的通信,通知链只能用于内核不同子系统之间的通信.那么内核通知链到底是怎么工作的?我们如何才能用好通知链?内核源代码里随处可见的通知链身影,我们到底该如何理解呢?本片博文过后,您的这些疑问和顾虑将统统消除.    以前有个女神,超凡脱俗.出水芙蓉,不过在怎么滴也是人,是人就会有各种各样的需

Linux内核通知链分析【转】

转自:http://www.cnblogs.com/jason-lu/articles/2807758.html Linux内核通知链分析 1. 引言 Linux是单内核架构(monolithic kernel),大多数内核子系统和模块是相互独立的,它们被动态地加载或卸载,以使内核变得小巧和可扩展.然而,子系统或模块之间需要通信,或者说某个特定模块扑捉到的事件可能其它模块对此感兴趣,这就需要一种机制来满足子系统或模块之间交互的需求. Linux使用通知链表来实现这一需求,它是一个简单的函数链表,

PHP 命令行模式实战之cli+mysql 模拟队列批量发送邮件(在Linux环境下PHP 异步执行脚本发送事件通知消息实际案例)

源码地址:https://github.com/Tinywan/PHP_Experience 测试环境配置: 环境:Windows 7系统 .PHP7.0.Apache服务器 PHP框架:ThinkPHP框架(3.2) Redis数据库:测试数据回调函数:通过一个Redis的自增incr来测试异步脚本执行的次数和访问的时间(平时都是用Redis测试写日志的) 编辑器:Visual Studio Code (CLI运行环境好看点) PHP 的命令行模式       从版本 4.3.0 开始,PHP