转自: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 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 ):通知链元素的回调函数(当事件发生时要执行的函数)在中断或原子操作上下文中运行,不允许阻塞。对应的链表头结构:

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

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

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

    转载: http://blog.csdn.net/wuhzossibility/article/details/8079025 http://blog.chinaunix.net/uid-277176 ...

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

    转自:http://www.cnblogs.com/pengdonglin137/p/4075148.html 阅读目录(Content) 1.1. 概述 1.2.数据结构 1.3.  运行机理 1. ...

  3. linux kernel notifier chain(事件通知链)

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

  4. Linux内核基础优化

    Linux内核基础优化 net.ipv4.ip_forward = 1 #开启网络转发 net.ipv4.conf.default.rp_filter = 0 #开启代理功能 net.ipv4.con ...

  5. [Linux] 内核通知链 notifier

    Linux 内核中每个模块之间都是独立的,如果模块需要感知其他模块的事件,就需要用到内核通知链. 最典型的通知链应用就是 LCD 和 TP 之间,TP 需要根据 LCD 的亮灭来控制是否打开关闭触摸功 ...

  6. linux内核基础(系统调用,简明)

    内核基础(系统调用) 在说系统调用之前.先来说说内核是怎么和我们交互的.或者说是怎么和我们产生交集的. 首先,内核是用来控制硬件的仅仅有内核才干直接控制硬件,所以说内核非常重要,假设内核被控制那么电脑 ...

  7. Linux内核基础

            Linux系统运行的应用程序通过系统调用来与内核通信.应用程序通常调用库函数(比如C库函数)再有库函数通过系统调用界面,让内核带其完成各种不同的任务. 下面这张图显示的就是应用程序,内 ...

  8. Jquery | 基础 | 事件的链式写法

    $(".title").click(function () { $(this).addClass("curcol").next(".content&q ...

  9. Linux 内核热插拔事件产生

    一个热插拔事件是一个从内核到用户空间的通知, 在系统配置中有事情已经改变. 无论何 时一个 kobject 被创建或销毁就产生它们. 这样事件被产生, 例如, 当一个数字摄像头 使用一个 USB 线缆 ...

随机推荐

  1. bzoj3998-弦论

    给定一个长度为\(n(n\le 5\times 10^5)\)的字符串,求它的第\(k\)小字串.有两种模式: \(Type=0\),不同位置的相同字串只算一个 \(Type=1\),不同位置相同字串 ...

  2. openstack之keystone部署

    前言 openstack更新频率是挺快的,每六个月更新一次(命名是是以A-Z的方式,Austin,Bexar...Newton).博主建议大家先可一种版本研究,等某一版本研究透彻了,在去研究新的版本. ...

  3. BZOJ4754 JSOI2016独特的树叶(哈希)

    判断两棵无根树是否同构只需要把重心提作根哈希即可.由于只添加了一个叶子,重心的位置几乎不发生偏移,所以直接把两棵树的重心提起来,逐层找哈希值不同且对应的两子树即可.被一个普及组子问题卡一年. #inc ...

  4. P4035 [JSOI2008]球形空间产生器

    题目描述 有一个球形空间产生器能够在 nn 维空间中产生一个坚硬的球体.现在,你被困在了这个 nn 维球体中,你只知道球面上 n+1n+1 个点的坐标,你需要以最快的速度确定这个 nn 维球体的球心坐 ...

  5. POJ3243:Clever Y——题解

    http://poj.org/problem?id=3243 求最小的非负整数y满足x^y=k(mod z) 写完板子之后等待了半个小时poj才终于进入…… poj不行啊.jpg 以前一直觉得BSGS ...

  6. [Leetcode] Binary tree postorder traversal二叉树后序遍历

    Given a binary tree, return the postorder traversal of its nodes' values. For example:Given binary t ...

  7. HDU 2710

    Max Factor Time Limit: 2000/1000 MS(Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total ...

  8. Basic Data Structure HDU - 5929 (这个模拟我要报警了)

    Mr. Frog learned a basic data structure recently, which is called stack.There are some basic operati ...

  9. stout代码分析之二:None类

    stout库中为了避免使用NULL带来的风险,统一用None表示空. None类的实现方式如下: struct None {}; 奇怪的是, Nothing类实现方式与None一模一样..让人怀疑作者 ...

  10. linux部署j2eeweb工程涉及到的指令

    1.查看java进程: ps -e | grep java; 可以获取到java进程的进程号. 或: ps -ef | grep java; 可以查看到详细的进程信息 2.杀死java进程 kill ...