为什么要有内核通知表链:

    Linux由多个相互依赖的子系统组成。其中一些子系统可能需要对其他子系统的一些事件感兴趣。这样子系统之间需要一些通信机制来实现这一功能。
    在接触Notification Chain之前,我们可能想到通过轮询来实现,事件发生时,子系统轮询所有其他的子系统,看看有没有对这一事件感兴趣的,有没有需要执行的子函数。
  1. If (subsystem_X_enabled) {
  2. do_something_1
  3. }
  4. if (subsystem_Y_enabled) {
  5. do_something_2
  6. }
  7. If (subsystem_Z_enabled) {
  8. do_something_3
  9. }
  10. ... ... ...

    这种方式看起来可以满足我们的需要,但是代码太繁杂了,会导致每个子系统变得很庞大。而Notification
Chain则大大的改善了这些不足。
Notification Chain基本原理
    Notification Chain是一种“发布——订阅”模型,当某个子系统发生某一事件时,它就通知所有对这一事件感兴趣的子系统,以便执行响应函数。在这个模型中,发生事件的子系统是主动端,对事件感兴趣的子系统是被动端。(本文也将被动端成为被通知端)。 在事件发生时,检测或产生事件的子系统作为主动一方通过通知函数来告知作为被动一方的订阅者(对此事件感兴趣的子系统)。这里有个额外要求,订阅一方要提供callback函数以供发布方调用,当然,提供什么样的callback函数完全由订阅方决定。

通知表链模型图:


Notification Chain源代码解析

   结构体:
  1. //通知块
  2. struct notifier_block
  3. {
  4. int (*notifier_call)(struct notifier_block *self, unsigned long, void *);//回调函数
  5. struct notifier_block *next; //
  6. int priority; //注册的优先级,用户自行指定,优先级越高回调函数就越早执行
  7. };

通知表链的定义
Linux内核中有三种通知表链:
  1. //原子通知表链
  2. struct atomic_notifier_head {
  3. spinlock_t lock;
  4. struct notifier_block __rcu *head;
  5. };
  6.  
  7. //可阻塞通知表链
  8. struct blocking_notifier_head {
  9. struct rw_semaphore rwsem;
  10. struct notifier_block __rcu *head;
  11. };
  12.  
  13. //原始通知表链
  14. struct raw_notifier_head {
  15. struct notifier_block __rcu *head;
  16. };
  17.  
  18. //可阻塞通知表链的变体
  19. struct srcu_notifier_head {
  20. struct mutex mutex;
  21. struct srcu_struct srcu;
  22. struct notifier_block __rcu *head;
  23. };

需要定义一个通知表链时,用XXX_NOTIFIER_HEAD(name)来定义,下面以RAW_NOTIFIER_HEAD为例:
  1. static RAW_NOTIFIER_HEAD(netdev_chain);

追踪 RAW_NOTIFIER_HEAD,
  1. #define ATOMIC_NOTIFIER_HEAD(name) \
  2. struct atomic_notifier_head name = \
  3. ATOMIC_NOTIFIER_INIT(name)
  4. #define BLOCKING_NOTIFIER_HEAD(name) \
  5. struct blocking_notifier_head name = \
  6. BLOCKING_NOTIFIER_INIT(name)
  7. #define RAW_NOTIFIER_HEAD(name) \
  8. struct raw_notifier_head name = \
  9. RAW_NOTIFIER_INIT(name)

继续
  1. #define ATOMIC_NOTIFIER_INIT(name) { \
  2. .lock = __SPIN_LOCK_UNLOCKED(name.lock), \
  3. .head = NULL }
  4. #define BLOCKING_NOTIFIER_INIT(name) { \
  5. .rwsem = __RWSEM_INITIALIZER((name).rwsem), \
  6. .head = NULL }
  7. #define RAW_NOTIFIER_INIT(name) { \
  8. .head = NULL }
  9. /* srcu_notifier_heads cannot be initialized statically */

由此完成了一条通知表链的定义和初始化。

    Linux 内核中有许多个notification chain,我们常用到的有三个netdev_chain,inetaddr_chain,inet6addr_chain。结合struct notifier_block,每条chain上有一个回调函数的队列(链表)notifier_call ,那么这些回调函数(notification通知块)如何使用,合适调用呢。
    首先我们在穿件通知表链之初,通知表链只有链表头,没有其他的notifier_block块,这个链表会关注一些事件(如netdev_chain会关注设备加入删除等,具体下文会做更清楚的介绍。)如果某个子系统对这条chain关注的时间感兴趣,它就注册一个notifier_block到链表中。notifier_block块的注册和注销,以下面形式实现:
  1. int notifier_chain_register(struct notifier_block **list, struct notifier_block *n)
  2. int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n)

这两个是封装函数,具体的通知链都会对这两个函数进行封装,定义自己的注册和注销函数。以netdev_chain为例,封装过程:
  1. int register_netdevice_notifier(struct notifier_block *nb)
  2. {
  3. raw_notifier_chain_register(&netdev_chain, nb);
  4. }
  5. int raw_notifier_chain_register(struct raw_notifier_head *nh,
  6. struct notifier_block *n)
  7. {
  8. return notifier_chain_register(&nh->head, n);
  9. }

某个子系统需要关注设备的变化信息时,就将处理函数notifier_call(自己编写的)作为notifier_block注册的通知表链。

    事件发生时,通知表链执行过程
    当事件发生时,时间会调用notifier_call_chain遍历通知表链中每个通知块里的回调函数,由回调函数确定是否要执行处理。
  1. static int __kprobes notifier_call_chain(struct notifier_block **nl,
  2. unsigned long val, void *v,
  3. int nr_to_call, int *nr_calls)
  4. {
  5. int ret = NOTIFY_DONE;
  6. struct notifier_block *nb, *next_nb;
  7.  
  8. nb = rcu_dereference_raw(*nl);
  9.  
  10. while (nb && nr_to_call) {
  11. next_nb = rcu_dereference_raw(nb->next);
  12.  
  13. #ifdef CONFIG_DEBUG_NOTIFIERS
  14. if (unlikely(!func_ptr_is_kernel_text(nb->notifier_call))) {
  15. WARN(1, "Invalid notifier called!");
  16. nb = next_nb;
  17. continue;
  18. }
  19. #endif
  20. ret = nb->notifier_call(nb, val, v);
  21.  
  22. if (nr_calls)
  23. (*nr_calls)++;
  24.  
  25. if ((ret & NOTIFY_STOP_MASK) == NOTIFY_STOP_MASK)
  26. break;
  27. nb = next_nb;
  28. nr_to_call--;
  29. }
  30. return ret;
  31. }


    
    






























深入理解Linux网络技术内幕——Notification内核通知表链的更多相关文章

  1. 深入理解linux网络技术内幕读书笔记(三)--用户空间与内核的接口

    Table of Contents 1 概论 1.1 procfs (/proc 文件系统) 1.1.1 编程接口 1.2 sysctl (/proc/sys目录) 1.2.1 编程接口 1.3 sy ...

  2. 深入理解linux网络技术内幕读书笔记(四)--通知链

    Table of Contents 1 概述 2 定义链 3 链注册 4 链上的通知事件 5 网络子系统的通知链 5.1 包裹函数 5.2 范例 6 测试实例 概述 [注意] 通知链只在内核子系统之间 ...

  3. 深入理解Linux网络技术内幕——设备的注册与初始化(二)

    设备注册于设备除名     设备注册与设备除名一般有 register_netdev和unregister_netdev完成.这两个是包裹函数,负责上锁,真正起作用的是其调用的register_net ...

  4. 深入理解Linux网络技术内幕——用户空间与内核空间交互

    概述:     内核空间与用户空间经常需要进行交互.举个例子:当用户空间使用一些配置命令如ifconfig或route时,内核处理程序就要响应这些处理请求.     用户空间与内核有多种交互方式,最常 ...

  5. 深入理解linux网络技术内幕读书笔记(七)--组件初始化的内核基础架构

    Table of Contents 1 引导期间的内核选项 2 注册关键字 3 模块初始化代码 引导期间的内核选项 linux运行用户把内核配置选项传给引导记录,然后引导记录再把选项传给内核. 在引导 ...

  6. 深入理解Linux网络技术内幕——内核基础架构和组件初始化

    引导期间的内核选项     Linux允许用户把内核配置选项传给引导记录,再有引导记录传给内核,以便对内核进行调整.     start_kernel中调用两次parse_args,用于引导期间配置用 ...

  7. 《深入理解Linux网络技术内幕》阅读笔记 --- 路由基本概念

    一.路由的基本概念 1.一条路由就是一组参数,这些参数存储了往一个给定目的地转发流量所需的信息,而一条路由所需的最少的参数集合为:(1)目的网络,(2)出口设备,(3)下一跳网关 2.路由中的相关术语 ...

  8. 深入理解linux网络技术内幕读书笔记(十)--帧的接收

    Table of Contents 1 概述 1.1 帧接收的中断处理 2 设备的开启与关闭 3 队列 4 通知内核帧已接收:NAPI和netif_rx 4.1 NAPI简介 4.1.1 NAPI优点 ...

  9. 深入理解linux网络技术内幕读书笔记(九)--中断与网络驱动程序

    Table of Contents 1 接收到帧时通知驱动程序 1.1 轮询 1.2 中断 2 中断处理程序 3 抢占功能 4 下半部函数 4.1 内核2.4版本以后的下半部函数: 引入软IRQ 5 ...

随机推荐

  1. java中new一个对象和对象=null有什么区别

    原创:转载请注明出处 今天在写代码时,遇到一个问题,特此进行记录. for (ProfileDto profileDto : profile)            { // Profile resP ...

  2. 04_Windows平台Spark开发环境构建

    Spark的开发环境,可以基于IDEA+Scala插件,最终将打包得到的jar文件放入Linux服务器上的Spark上运行 如果是Python的小伙伴,可以在Windows上部署spark+hadoo ...

  3. 51nod 1307 绳子与重物(并查集水了一发)

    http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1307 题意: 思路: 可以直接二分答案,然后dfs. 因为标签是并查集, ...

  4. FAILED Selenium2Library

    FAILED Selenium2Library Importing test library 'Selenium2Library' failed: ImportError: cannot import ...

  5. fusion--RNAseq

    融合基因(Fusion gene)是指两个基因的全部或一部分的序列相互融合为一个新的基因的过程.其有可能是染色体易位.中间缺失或染色体倒置所致的结果. 异常的融合基因可以引起恶性血液疾病以及肿瘤.例如 ...

  6. Java中Arrays 与 Collections 的简单操作

    import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.C ...

  7. python写入csv文件的几种方法总结

    生成test.csv文件 #coding=utf- import pandas as pd #任意的多组列表 a = [,,] b = [,,] #字典中的key值即为csv中列名 dataframe ...

  8. Jmeter ResponseAssertion 【Ignore Status】

    在Jmeter源码中AssertionGui.java中,定义了Ignore Status的作用域 /** * Checkbox to indicate whether the response sh ...

  9. xss脚本注入后端的防护

    1.脚本注入最主要的是不要相信用户输入的任何数据,对数据进行转义 可以使用:org.springframework.web.util.HtmlUtils包中的 HtmlUtils.htmlEscape ...

  10. listener TNS-01189 问题

    -- 启动监听,提示已经启动. [oracle@sh ~]$ lsnrctl start LSNRCTL for Linux: Version 12.1.0.2.0 - Production on 0 ...