Linux内核通知链notifier

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

通知链技术可以概括为:事件的被通知者将事件发生时应该执行的操作通过函数指针方式保存在链表(通知链)中,然后当事件发生时通知者依次执行链表中每一个元素的回调函数完成通知。

2.内核通知链表数据结构
    通知链表的节点类型为notifier_block,其定义如下:

struct notifier_block {

int (*notifier_call)(struct notifier_block *, unsigned long, void *);

struct notifier_block *next;

int priority;
};

notifier_call:该节点所对应的要运行的函数。
*next:指向下一个节点,事件发生时,依次执行的

3.内核通知链注册函数:
    在通知链注册时,需要有一个链表头,它指向这个通知链表的第一个元素。这样,之后的事件对该链表通知时就会根据这个链表头而找到这个链表中所有的元素。链表头的定义见本文第6节。
    注册的函数是:

/*

*
Notifier chain core routines.
The exported routines below

*
are layered on top of these, with appropriate locking added.

*/

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_register 函数注册回调函数,该函数是按照优先级将回调函数加入到通知链中去的。

卸载的函数是:
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;
}

将节点n从nl所指向的链表中删除。
在kernel/notifier.c中内核根据通知链的类型分别包装了下面这几个函数:

int atomic_notifier_chain_register(struct atomic_notifier_head *nh,

struct notifier_block *n)

int blocking_notifier_chain_register(struct blocking_notifier_head *nh,

struct notifier_block *n)

int raw_notifier_chain_register(struct raw_notifier_head *nh,

struct notifier_block *n)

int srcu_notifier_chain_register(struct srcu_notifier_head *nh,

struct notifier_block *n)

4.内核通知链通知函数:
    当有事件发生时,通知者调用 notifier_call_chain 函数通知事件的到达,这个函数会遍历n1指向的通知链中所有的元素,然后依次调用每一个的回调函数,完成通知动作。

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;
}

在kernel/notifier.c中内核根据通知链的类型分别包装了上面这个函数:

int atomic_notifier_call_chain(struct atomic_notifier_head *nh,

unsigned long val, void *v)

int blocking_notifier_call_chain(struct blocking_notifier_head *nh,

unsigned long val, void *v)

int raw_notifier_call_chain(struct raw_notifier_head *nh,

unsigned long val, void *v)

int srcu_notifier_call_chain(struct srcu_notifier_head *nh,

unsigned long val, void *v)

5.通知链四种类型

(5.1)原子通知链的链头
通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,不允许阻塞。
struct atomic_notifier_head {

spinlock_t lock;

struct notifier_block *head;
};
(5.2)可阻塞通知链:
通知链元素的回调函数在进程上下文中运行,允许阻塞。
struct blocking_notifier_head {

struct rw_semaphore rwsem;

struct notifier_block *head;
};
(5.3)原始通知链:
对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。
struct raw_notifier_head {

struct notifier_block *head;
};
(5.4)SRCU 通知链:
可阻塞通知链的变种。
struct srcu_notifier_head {

struct mutex mutex;

struct srcu_struct srcu;

struct notifier_block *head;
};

6)定义一个通知链的头部结点并初始化:
include/linux/Notifier.h
初始化宏定义:

#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 */

定义通知链:
#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)

Linux内核调试方法总结之内核通知链的更多相关文章

  1. 【转】Linux内核调试方法总结

    目录[-] 一  调试前的准备 二  内核中的bug 三  内核调试配置选项 1  内核配置 2  调试原子操作 四  引发bug并打印信息 1  BUG()和BUG_ON() 2  dump_sta ...

  2. Linux内核调试方法总结

    Linux内核调试方法总结 一  调试前的准备 二  内核中的bug 三  内核调试配置选项 1  内核配置 2  调试原子操作 四  引发bug并打印信息 1  BUG()和BUG_ON() 2   ...

  3. Linux内核调试方法总结之反汇编

    Linux反汇编调试方法 Linux内核模块或者应用程序经常因为各种各样的原因而崩溃,一般情况下都会打印函数调用栈信息,那么,这种情况下,我们怎么去定位问题呢?本文档介绍了一种反汇编的方法辅助定位此类 ...

  4. Linux内核调试方法总结之序言

    本系列主要介绍Linux内核死机.异常重启类稳定性问题的调试方法. 在Linux系统中,一切皆为文件,而系统运行的载体,是一类特殊的文件,即进程.因此,我尝试从进程的角度分析Linux内核的死机.异常 ...

  5. Linux内核调试方法总结【转】

    转自:http://my.oschina.net/fgq611/blog/113249 内核开发比用户空间开发更难的一个因素就是内核调试艰难.内核错误往往会导致系统宕机,很难保留出错时的现场.调试内核 ...

  6. Linux内核调试方法【转】

    转自:http://www.cnblogs.com/shineshqw/articles/2359114.html kdb:只能在汇编代码级进行调试: 优点是不需要两台机器进行调试. gdb:在调试模 ...

  7. Linux内核调试方法总结之ptrace

    ptrace [用途] 进程跟踪器,类似于gdb watch的调试方法 [原理][详细说明参考man ptrace帮助文档] ptrace系统调用主要是父进程用来观察和控制子进程的执行过程.检查并替换 ...

  8. Linux内核调试方法总结之ddebug

    [用途] Linux内核动态调试特性,适用于驱动和内核各子系统调试.动态调试的主要功能就是允许你动态的打开或者关闭内核代码中的各种提示信息.适用于驱动和内核线程功能调试. [使用方法] 依赖于CONF ...

  9. Linux内核调试方法总结之调试宏

    本文介绍的内核调试宏属于静态调试方法,通过调试宏主动触发oops从而打印出函数调用栈信息. 1) BUG_ON 查看bug处堆栈内容,主动制造oops Linux中BUG_ON,WARN_ON用于调试 ...

随机推荐

  1. IntelliJ IDEA 部署 Web 项目,终于搞懂了!

    这篇牛逼: IDEA 中最重要的各种设置项,就是这个 Project Structre 了,关乎你的项目运行,缺胳膊少腿都不行. 最近公司正好也是用之前自己比较熟悉的IDEA而不是Eclipse,为了 ...

  2. 数位dp相关

    经典的数位Dp是要求统计符合限制的数字的个数. 一般的形式是:求区间[n,m]满足限制f(1). f(2). f(3)等等的数字的数量是多少. 条件 f(i) 一般与数的大小无关,而与数的组成有关. ...

  3. The kth great number

    The kth great number Problem Description Xiao Ming and Xiao Bao are playing a simple Numbers game. I ...

  4. [LeetCode] 164. 最大间距

    题目链接 : https://leetcode-cn.com/problems/maximum-gap/ 题目描述: 给定一个无序的数组,找出数组在排序之后,相邻元素之间最大的差值. 如果数组元素个数 ...

  5. Java中HashSet和HashMap

    Set中存储元素为什么不重复(即使hashCode相同)? HashSet中存放自定义类型元素时候,需要重写对象中的hashCode方法和equals方法, HashSet中存放自定义类型元素时候,需 ...

  6. 小白如何入门 Python 爬虫?

    本文针对初学者,我会用最简单的案例告诉你如何入门python爬虫! 想要入门Python 爬虫首先需要解决四个问题 熟悉python编程 了解HTML 了解网络爬虫的基本原理 学习使用python爬虫 ...

  7. AXI总线协议

    AXI总线协议 (一).概述 AXI (高性能扩展总线接口,Advanced eXtensible Interface)是ARM AMBA 单片机总线系列中的一个协议,是计划用于高性能.高主频的系统设 ...

  8. Redis主从架构核心原理

    Redis-Cluster工作原理: redis集群内置了16384个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果 ...

  9. Agreeing to the Xcode/iOS license requires admin privileges, please run “sudo xcodebuild -license” a...

    报错: 从错误信息来看,似乎需要通过管理员身份来接受许可协议,于是试着从这个角度google,终于在这里找到了解决方法: 1.打开终端,输入  sudo xcodebuild -license 2.终 ...

  10. HTML-图片和多媒体

    1.图片和多媒体 (1)    图片:img元素 src 属性:图片路径: alt 属性:图片无法显示时使用的替代文字: title:鼠标悬停时显示的文字 : <img src="图片 ...