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

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

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

通知表链模型图:


Notification Chain源代码解析

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

通知表链的定义
Linux内核中有三种通知表链:
//原子通知表链
struct atomic_notifier_head {
spinlock_t lock;
struct notifier_block __rcu *head;
}; //可阻塞通知表链
struct blocking_notifier_head {
struct rw_semaphore rwsem;
struct notifier_block __rcu *head;
}; //原始通知表链
struct raw_notifier_head {
struct notifier_block __rcu *head;
}; //可阻塞通知表链的变体
struct srcu_notifier_head {
struct mutex mutex;
struct srcu_struct srcu;
struct notifier_block __rcu *head;
};

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

追踪 RAW_NOTIFIER_HEAD,
#define ATOMIC_NOTIFIER_HEAD(name)              \
struct atomic_notifier_head name = \
ATOMIC_NOTIFIER_INIT(name)
#define BLOCKING_NOTIFIER_HEAD(name) \
struct blocking_notifier_head name = \
BLOCKING_NOTIFIER_INIT(name)
#define RAW_NOTIFIER_HEAD(name) \
struct raw_notifier_head name = \
RAW_NOTIFIER_INIT(name)

继续
#define ATOMIC_NOTIFIER_INIT(name) {                \
.lock = __SPIN_LOCK_UNLOCKED(name.lock), \
.head = NULL }
#define BLOCKING_NOTIFIER_INIT(name) { \
.rwsem = __RWSEM_INITIALIZER((name).rwsem), \
.head = NULL }
#define RAW_NOTIFIER_INIT(name) { \
.head = NULL }
/* 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块的注册和注销,以下面形式实现:
int notifier_chain_register(struct notifier_block **list, struct notifier_block *n)
int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n)

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

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

    事件发生时,通知表链执行过程
    当事件发生时,时间会调用notifier_call_chain遍历通知表链中每个通知块里的回调函数,由回调函数确定是否要执行处理。
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_raw(*nl); while (nb && nr_to_call) {
next_nb = rcu_dereference_raw(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;
}


    
    






























深入理解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. CodeForces 1029E div3

    题目链接 第一道场上自己做出来的E题...虽然是div3,而且是原题... 当时做完ABC,D题没有思路就去怼E了,然后发现貌似原题? 事实上就是原题... 给个原题链接... [HNOI2003]消 ...

  2. guulp配置编译ES6

    下面是gulp的配置文件,gulp具体使用点击查看 首先全局安装下 cnpm install gulp -g gulpfile.js  gulp配置文件 var gulp = require(&quo ...

  3. gulp报错插件gulp-notify 配置项

    var notify = require("gulp-notify"); module.exports = function(){ var args = Array.prototy ...

  4. hdu 3183 A Magic Lamp rmq或者暴力

    A Magic Lamp Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Pro ...

  5. 一个十分好用的动画工具:Velocity.js

    就像许多开发者确信的那样,在Web上使用CSS实现动画并不是唯一的方式,我们也可以使用JS来实现,并且JS还有一些CSS无法替代的优势. 然而抛开JS而选择CSS来实现动画,将以样式表内容膨胀,丧失对 ...

  6. Qt与FFmpeg联合开发指南(一)——解码(1):功能实现

    前言:对于从未接触过音视频编解码的同学来说,使用FFmpeg的学习曲线恐怕略显陡峭.本人由于工作需要,正好需要在项目中使用.因此特地将开发过程总结下来.只当提供给有兴趣的同学参考和学习. 由于FFmp ...

  7. Codeforces 680D - Bear and Tower of Cubes

    680D - Bear and Tower of Cubes 思路:dfs+贪心,设剩余的体积为res,存在a,使得a3 ≤ res,每次取边长为a的立方体或者边长为a-1的立方体(这时体积上限变成a ...

  8. openshift harp.js heroku react-router 4

    https://segmentfault.com/a/1190000000355181 http://harpjs.com/ https://www.jianshu.com/p/7bc34e56fa3 ...

  9. SpringBoot读取war包jar包Resource资源文件解决办法

    SpringBoot读取war包jar包Resource资源文件解决办法 场景描述 在开发过程中我们经常会碰到要在代码中获取资源文件的情况,而我在最近在SpringBoot项目中时碰到一个问题,就是在 ...

  10. 20170601xlVBA正则表达式提取体检数据

    Public Sub GetFirst() GetDataFromWord "初检" End Sub Public Sub GetDataFromWord(ByVal SheetN ...