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. W同学的新画板 QDUOJ 线段树 区间颜色段数

    W同学的新画板 QDUOJ 线段树 区间颜色段数 原题链接 题意 W同学在每天的刻苦学习完成功课之余,都会去找一些有趣的事情来放松自己:恰巧今天他收到了朋友送给他的一套画板,于是他立刻拆开了包装,拿出 ...

  2. java中的byte有什么作用?

    byte即字节的意思,是java中的基本类型,用心申明字节型的变量. 通常在读取非文本文件时(如图片,声音,可执行文件)需要用字节数组来保存文件的内容,在下载文件时,也是用byte数组作临时的缓冲器接 ...

  3. egret 发布ios记录

    根据官方文档http://developer.egret.com/cn/github/egret-docs/Native/native/hybrid/hybrid/index.html 将现有的项目发 ...

  4. mplayer - Linux下的电影播放器

    概要 mplayer [选项] [ 文件 | URL | 播放列表 | - ] mplayer [全局选项] 文件1 [特定选项] [文件2] [特定选项] mplayer [全局选项] {一组文件和 ...

  5. 16.Linux-CentOS系统进入单用户模式修改root用户密码操作

    问题描述: root用户密码忘记,进入单用户重置root用户密码 解决步骤: 1.重启服务器,在系统显示内核版本界面后“按E键”,进入内核启动项2.找到Linux16这一行段,将“ro”修改成“rw” ...

  6. 美国Science公布:全球125个最前沿的科学难题(图)

    文章来源:https://www.toutiao.com/i6637224168045675021 美国Science在庆祝创刊125周年之际,公布了125个最具挑战性的科学问题.这些前沿科学和研究方 ...

  7. [apache spark]洞见纽约车辆事故|bluemix|apache spark

    今天,我们用spark 来分析 下一纽约市车辆事故的大数据. 前提条件: 1.有bluemix 帐号,并并在bluemix的dashboard里创建了一个sparck instance. 2.稳定可以 ...

  8. GUI学习之二十一——QSlider、QScroll、QDial学习总结

    上一章我们学习了QAbstractSlider的用法,在讲功能的时候我们是借助了它的子类QSlider来实现的,今天来学习一下它的三个子类——QSlider.QScroll和QDial. 一.QSli ...

  9. 错误:非法字符:“\ufeff”

        导入开源的项目的时候,你可以碰到以上的编码问题,这一般这个项目是用eclipse开发的.主要原因是: Eclipse可以自动把UTF-8+BOM文件转为普通的UTF-8文件,但Android ...

  10. php内置函数分析之strpos()

    PHP_FUNCTION(strpos) { zval *needle; zend_string *haystack; char *found = NULL; ]; zend_long offset ...