Linux内核:通知链 机制

背景

在驱动分析中经常看到fb_notifier_callback,现在趁有空学习一下。

参考:

概念

大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣。为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子系统,Linux内核提供了通知链的机制。通知链表只能够在内核的子系统之间使用,而不能够在内核与用户空间之间进行事件的通知。 通知链表是一个函数链表,链表上的每一个节点都注册了一个函数。当某个事情发生时,链表上所有节点对应的函数就会被执行。所以对于通知链表来说有一个通知方与一个接收方。在通知这个事件时所运行的函数由被通知方决定,实际上也即是被通知方注册了某个函数,在发生某个事件时这些函数就得到执行。

其实通知链和系统调用signal的思想差不多。

先考虑这么一种场景:

对于网卡驱动子系统来说,经常会发生的情况就是什么?网卡IP地址有变化,网卡状态有变化等等。

那么如果有其他子系统,比如路由子系统最网卡IP地址变化这件事比较感兴趣,它该怎么去感知这件事儿呢?

通知链机制可以算作是“订阅者-发布者”模型的一种。

每个子系统都会有些一些重要事件,例如前面说的,网络驱动子系统网卡的事件,或者USB的状态事件等等;

这些子系统都会提供一个自己的事件队列,这个队列都是其他函数提供的回调函数:当有事件发生时,子系统就会去遍历其事件队列上已经注册了的所有回调函数,这样就实现了“通知”的目的。

通知链的原型就是一条单向链表,内核提供的通知链机制主要用于不同子系统之间通信,基于事件和优先级。

这部分是由协议栈头号大Boss----@Alan Cox亲自主刀,足以说明这个基础特性的重要性,也有很多值得我们学习的地方。

数据结构

不同类型的通知链

Linux内核提供了三类通知链:原子通知链、阻塞通知链和原始通知链,它们的主要区别就是在执行通知链上的回调函数时是否有安全保护措施。

原子通知链

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

struct atomic_notifier_head
{
spinlock_t lock;
struct notifier_block *head;
};

可阻塞通知链

可阻塞的通知链有两种类型,一种用信号量实现回调函数的加锁,另一种是采用互斥锁和叫做“可睡眠的读拷贝更新机制”(Sleepable Read-Copy UpdateSleepable Read-Copy Update)。

可阻塞型的通知链运行在进程空间的上下文环境里。

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

struct blocking_notifier_head
{
struct rw_semaphore rwsem;
struct notifier_block *head;
};

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

struct srcu_notifier_head
{
struct mutex mutex;
struct srcu_struct srcu;
struct notifier_block *head;
};

原始通知链

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

struct raw_notifier_head
{
struct notifier_block *head;
};

核心结构

通知链的核心结构:

struct notifier_block
{
int (*notifier_call)(struct notifier_block *, unsigned long, void *);
struct notifier_block *next;
int priority;
};

参数:

  • 最重要的就是notifier_call这个函数指针,代表通知链要执行的函数指针,

  • next指向下一个回调函数的通知块

  • priority是这个通知的优先级,同一条链上的notifier_block是按优先级排列的。priority是事件发生时本函数(由notifier_call所指向)执行的优先级,数字越大优先级越高,越会先被执行。

内核代码中一般把通知链命名为xxx_chain, xxx_nofitier_chain这种形式的变量名。

运作机制

通知链的运作机制包括两个角色:

  • 被通知者:对某一事件感兴趣一方。定义了当事件发生时,相应的处理函数,即回调函数。但需要事先将其注册到通知链中(被通知者注册的动作就是在通知链中增加一项)。
  • 通知者:事件的通知者。当检测到某事件,或者本身产生事件时,通知所有对该事件感兴趣的一方事件发生。他定义了一个通知链,其中保存了每一个被通知者对事件的处理函数(回调函数)。通知这个过程实际上就是遍历通知链中的每一项,然后调用相应的事件处理函数。

包括以下过程:

1、通知者定义通知链。

2、被通知者向通知链中注册回调函数。

3、当事件发生时,通知者发出通知(执行通知链中所有元素的回调函数)。

监听通知

被通知者调用 notifier_chain_register 函数注册回调函数,该函数按照优先级将回调函数加入到通知链中:

static int notifier_chain_register(struct notifier_block **nl, struct notifier_block *n)
{
while ((*nl) != NULL)
{
if (n->priority > (*nl)->priority)
break;
nl = &((*nl)->next);
} n->next = *nl;
rcu_assign_pointer(*nl, n); return 0;
}

卸载通知

注销回调函数则使用 notifier_chain_unregister 函数,即将回调函数从通知链中删除:

static int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n)
{
while ((*nl) != NULL)
{
if ((*nl) == n)
{
rcu_assign_pointer(*nl, n->next);
return 0;
} nl = &((*nl)->next);
} return -ENOENT;
}

通知事件

通知者调用 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(*nl); while (nb && nr_to_call)
{
next_nb = rcu_dereference(nb->next); 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;
}

参数nl是通知链的头部,val表示事件类型,v用来指向通知链上的函数执行时需要用到的参数,一般不同的通知链,参数类型也不一样。

例如当通知一个网卡被注册时,v就指向net_device结构,nr_to_call表示准备最多通知几个,-1表示整条链都通知,nr_calls非空的话,返回通知了多少个。

每个被执行的notifier_block回调函数的返回值可能取值为以下几个:

  1. NOTIFY_DONE:表示对相关的事件类型不关心。
  2. NOTIFY_OK:顺利执行。
  3. NOTIFY_BAD:执行有错。
  4. NOTIFY_STOP:停止执行后面的回调函数。
  5. NOTIFY_STOP_MASK:停止执行的掩码。

notifier_call_chain()把最后一个被调用的回调函数的返回值作为它的返回值。

Linux内核:通知链 机制的更多相关文章

  1. Linux 内核通知链机制的原理及实现

    一.概念: 大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣.为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子 系统,Linux内核提供了通知链的机制.通 ...

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

    转自:http://www.cnblogs.com/armlinux/archive/2011/11/11/2396781.html 一.概念: 大多数内核子系统都是相互独立的,因此某个子系统可能对其 ...

  3. Linux 内核通知链随笔【中】

    关于内核通知链不像Netlink那样,既可以用于内核与用户空间的通信,还能用于内核不同子系统之间的通信,通知链只能用于内核不同子系统之间的通信.那么内核通知链到底是怎么工作的?我们如何才能用好通知链? ...

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

    转自:http://blog.chinaunix.net/uid-23069658-id-4364171.html 关于内核通知链不像Netlink那样,既可以用于内核与用户空间的通信,还能用于内核不 ...

  5. [Linux] 内核通知链 notifier

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

  6. Linux内核通知链模块

    通知链描写叙述 大多数内核子系统都是相互独立的,因此某个子系统可能对其他子系统产生的事件感兴趣. 为了满足这个需求,也即是让某个子系统在发生某个事件时通知其他的子系统.Linux内核提供了通知链的机制 ...

  7. Linux内核调试方法总结之内核通知链

    Linux内核通知链notifier 1.内核通知链表简介(引用网络资料)    大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣.为了满足这个需求,也即是让某个子系统在 ...

  8. 从基本理解到深入探究 Linux kernel 通知链(notifier chain)【转】

    转自:https://blog.csdn.net/u014134180/article/details/86563754 版权声明:本文为博主原创文章,未经博主允许不得转载.——Wu_Being ht ...

  9. Linux内核通知链分析【转】

    转自:http://www.cnblogs.com/jason-lu/articles/2807758.html Linux内核通知链分析 1. 引言 Linux是单内核架构(monolithic k ...

  10. Linux内核态抢占机制分析(转)

    Linux内核态抢占机制分析  http://blog.sina.com.cn/s/blog_502c8cc401012pxj.html 摘 要]本文首先介绍非抢占式内核(Non-Preemptive ...

随机推荐

  1. linux 环境下idea 注册过期或激活异常解决

    //@desn:linux 环境下idea 注册过期或激活异常解决 //@desn:码字不宜,转载请注明出处 //@author:张慧源  <turing_zhy@163.com> //@ ...

  2. Maven的概述

    Maven的概述 @ 目录 Maven的概述 2. 依赖 3. Maven 的工作机制 3. 最后:感谢 Java 项目开发过程中,构建指的是使用『原材料生产产品』的过程. 原材料 Java 源代码 ...

  3. python执行SQL语句中有格式化时是否需要加引号

    import pymysqldb = pymysql.connect(host='localhost', user='root', password='123456', database='test' ...

  4. 聊聊流言协议(Gossip)

    什么是流言协议? 在分布式系统中,以下两个是典型的问题: 维护系统状态(节点的活跃性) 节点间的通信 解决这些问题的解决方案之一如下: 集中式状态管理服务 对等状态管理服务 集中式状态管理服务 像 A ...

  5. Go:基于 MongoDB 构建 REST API — Fiber 版

    本文主要在于带着大家完成一个基本的 CRUD 工作,让大家熟悉 Fiber + MongoDB 构建 RESTful API,熟悉工作流程.同时了解相关库的使用. 本文的完整代码见:https://g ...

  6. 网络拓扑—NAT内外网映射

    使用Windows Server 2003 网络拓扑 Router 外网:NATIP 网段 = 192.168.17.0/24 内网:仅主机模式IP = 172.16.29.4 Client1:仅主机 ...

  7. 密码学—重合指数法Python程序

    重合指数(Ic) 计算重合指数就是用来验证在Kasiski测试法中猜测出来的各种密钥长度哪一个才是最接近真实密钥长度的. 计算重合指数步骤 按照Kasiski测试法猜测的密钥长度分组 ↓ 分好组之后将 ...

  8. turltle模块详解

    引言:turtle(海龟)模块,我们是用它来进行画图的,基本上就是画简单的直线,点,和曲线. 你可以把它想成一个小海龟,在沙滩上行走,然后留下的各种痕迹,使用Turtle模块可以绘制很多精美的图形. ...

  9. 记录一次WhatTheFuck经历

    起因 很早之前就一直在维护一个git仓库,平时调研什么组件就会在里面新建一个springboot的工程用来编写示例代码. 最一开始使用的是SpringInitializr,后来网站更新之后,只能生成J ...

  10. 《iOS面试之道》-勘误2

    一.如何保证NSTimer不受Runloop的影响,准时触发 书中提到两种方案, 一种是改变timer加入到runloop中的Mode,为CommonModes不受Runloop的Mode影响 第二种 ...