一、概念:

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

二、数据结构:

通知链有四种类型:

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

struct atomic_notifier_head

{

spinlock_t lock;

struct notifier_block *head;

};

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

struct blocking_notifier_head

{

struct rw_semaphore rwsem;

struct notifier_block *head;

};

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

struct raw_notifier_head

{

struct notifier_block *head;

};

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

struct srcu_notifier_head

{

struct mutex mutex;

struct srcu_struct srcu;

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{}是按优先级排列的。内核代码中一般把通知链命名为xxx_chain, xxx_nofitier_chain这种形式的变量名。

三、运作机制:

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

  1. 被通知者:对某一事件感兴趣一方。定义了当事件发生时,相应的处理函数,即回调函数。但需要事先将其注册到通知链中(被通知者注册的动作就是在通知链中增加一项)。

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

包括以下过程:

  1. 通知者定义通知链。

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

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

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

staticint 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 函数,即将回调函数从通知链中删除:

staticint 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 函数通知事件的到达,这个函数会遍历通知链中所有的元素,然后依次调用每一个的回调函数(即完成通知动作):

staticint __kprobes notifier_call_chain(struct notifier_block **nl,unsignedlong 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);

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

}

参数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()把最后一个被调用的回调函数的返回值作为它的返回值。

四、举例应用:

在这里,写了一个简单的通知链表的代码。实际上,整个通知链的编写也就两个过程:

  1. 首先是定义自己的通知链的头节点,并将要执行的函数注册到自己的通知链中。

  2. 其次则是由另外的子系统来通知这个链,让其上面注册的函数运行。

这里将第一个过程分成了两步来写,第一步是定义了头节点和一些自定义的注册函数(针对该头节点的),第二步则是使用自定义的注册函数注册了一些通知链节点。分别在代码buildchain.c与regchain.c中。发送通知信息的代码为notify.c。

代码1 buildchain.c。它的作用是自定义一个通知链表test_chain,然后再自定义两个函数分别向这个通知链中加入或删除节点,最后再定义一个函数通知这个test_chain链:

#include <asm/uaccess.h>

#include <linux/types.h>

#include <linux/kernel.h>

#include <linux/sched.h>

#include <linux/notifier.h>

#include <linux/init.h>

#include <linux/types.h>

#include <linux/module.h>

MODULE_LICENSE("GPL");/*

* 定义自己的通知链头结点以及注册和卸载通知链的外包函数

*//*

* RAW_NOTIFIER_HEAD是定义一个通知链的头部结点,

* 通过这个头部结点可以找到这个链中的其它所有的notifier_block

*/

static RAW_NOTIFIER_HEAD(test_chain);

/*

* 自定义的注册函数,将notifier_block节点加到刚刚定义的test_chain这个链表中来

* raw_notifier_chain_register会调用notifier_chain_register

*/

int register_test_notifier(struct notifier_block *nb)

{

return raw_notifier_chain_register(&test_chain, nb);

}

EXPORT_SYMBOL(register_test_notifier);

int unregister_test_notifier(struct notifier_block *nb)

{

return raw_notifier_chain_unregister(&test_chain, nb);

}

EXPORT_SYMBOL(unregister_test_notifier);

/*

* 自定义的通知链表的函数,即通知test_chain指向的链表中的所有节点执行相应的函数

*/

int test_notifier_call_chain(unsigned long val, void *v)

{

return raw_notifier_call_chain(&test_chain, val, v);

}

EXPORT_SYMBOL(test_notifier_call_chain);

/*

* init and exit

*/

static int __init init_notifier(void)

{

printk("init_notifiern");

return 0;

}

static void __exit exit_notifier(void)

{

printk("exit_notifiern");

}

module_init(init_notifier);

module_exit(exit_notifier);

代码2 regchain.c。该代码的作用是将test_notifier1 test_notifier2 test_notifier3这三个节点加到之前定义的test_chain这个通知链表上,同时每个节点都注册了一个函数:

#include <asm/uaccess.h>

#include <linux/types.h>

#include <linux/kernel.h>

#include <linux/sched.h>

#include <linux/notifier.h>

#include <linux/init.h>

#include <linux/types.h>

#include <linux/module.h>

MODULE_LICENSE("GPL");/*

* 注册通知链

*/

extern int register_test_notifier(struct notifier_block*);

extern int unregister_test_notifier(struct notifier_block*);static int test_event1(struct notifier_block *this, unsigned long event, void *ptr)

{

printk("In Event 1: Event Number is %dn", event);

return 0;

}

static int test_event2(struct notifier_block *this, unsigned long event, void *ptr)

{

printk("In Event 2: Event Number is %dn", event);

return 0;

}

static int test_event3(struct notifier_block *this, unsigned long event, void *ptr)

{

printk("In Event 3: Event Number is %dn", event);

return 0;

}

/*

* 事件1,该节点执行的函数为test_event1

*/

static struct notifier_block test_notifier1 =

{

.notifier_call = test_event1,

};

/*

* 事件2,该节点执行的函数为test_event1

*/

static struct notifier_block test_notifier2 =

{

.notifier_call = test_event2,

};

/*

* 事件3,该节点执行的函数为test_event1

*/

static struct notifier_block test_notifier3 =

{

.notifier_call = test_event3,

};

/*

* 对这些事件进行注册

*/

static int __init reg_notifier(void)

{

int err;

printk("Begin to register:n");

err = register_test_notifier(&test_notifier1);

if (err)

{

printk("register test_notifier1 errorn");

return -1;

}

printk("register test_notifier1 completedn");

err = register_test_notifier(&test_notifier2);

if (err)

{

printk("register test_notifier2 errorn");

return -1;

}

printk("register test_notifier2 completedn");

err = register_test_notifier(&test_notifier3);

if (err)

{

printk("register test_notifier3 errorn");

return -1;

}

printk("register test_notifier3 completedn");

return err;

}

/*

* 卸载刚刚注册了的通知链

*/

static void __exit unreg_notifier(void)

{

printk("Begin to unregistern");

unregister_test_notifier(&test_notifier1);

unregister_test_notifier(&test_notifier2);

unregister_test_notifier(&test_notifier3);

printk("Unregister finishedn");

}

module_init(reg_notifier);

module_exit(unreg_notifier);

代码3 notify.c。该代码的作用就是向test_chain通知链中发送消息,让链中的函数运行:

#include <asm/uaccess.h>

#include <linux/types.h>

#include <linux/kernel.h>

#include <linux/sched.h>

#include <linux/notifier.h>

#include <linux/init.h>

#include <linux/types.h>

#include <linux/module.h>

MODULE_LICENSE("GPL");extern int test_notifier_call_chain(unsigned long val, void *v);/*

* 向通知链发送消息以触发注册了的函数

*/

static int __init call_notifier(void)

{

int err;

printk("Begin to notify:n");

/*

* 调用自定义的函数,向test_chain链发送消息

*/

printk("==============================n");

err = test_notifier_call_chain(1, NULL);

printk("==============================n");

if (err)

printk("notifier_call_chain errorn");

return err;

}

static void __exit uncall_notifier(void)

{

printk("End notifyn");

}

module_init(call_notifier);

module_exit(uncall_notifier);

Makefile文件:

obj-m:=buildchain.o regchain.o notify.o

CURRENT_PATH := $(shell pwd)

LINUX_KERNEL := $(shell uname -r)

KERNELDIR := /usr/src/linux-headers-$(LINUX_KERNEL)all:

make -C $(KERNELDIR) M=$(CURRENT_PATH) modulesclean:

make -C $(KERNELDIR) M=$(CURRENT_PATH) clean

运行(注意insmod要root权限):

make

insmod buildchain.ko

insmod regchain.ko

insmod notify.ko

这样就可以看到通知链运行的效果了:

init_notifier

Begin to register:

register test_notifier1 completed

register test_notifier2 completed

register test_notifier3 completed

Begin to notify:

==============================

In Event 1: Event Number is 1

In Event 2: Event Number is 1

In Event 3: Event Number is 1

稿源:七星互联Qixoo.com

Linux 内核通知链机制的原理及实现的更多相关文章

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

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

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

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

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

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

  4. [Linux] 内核通知链 notifier

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

  5. Linux内核通知链模块

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

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

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

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

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

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

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

  9. Linux内核中锁机制之内存屏障、读写自旋锁及顺序锁

    在上一篇博文中笔者讨论了关于原子操作和自旋锁的相关内容,本篇博文将继续锁机制的讨论,包括内存屏障.读写自旋锁以及顺序锁的相关内容.下面首先讨论内存屏障的相关内容. 三.内存屏障 不知读者是是否记得在笔 ...

随机推荐

  1. 梳理git分支管理策略

    如果你严肃对待编程,就必定会使用"版本管理系统"(Version Control System). 眼下最流行的"版本管理系统",非Git莫属. 相比同类软件, ...

  2. Go 命令之 godep

    本文参考:http://www.cnblogs.com/me115/p/5528463.html#h20 http://studygolang.com/articles/4385 关于Godep 发现 ...

  3. Elasticsearch 相关名词理解

    Cluster包含多个node,Indices不应该理解成动词索引,Indices可理解成关系数据库中的databases,Indices可包含多个Index,Index对应关系数据库中的databa ...

  4. 解决SQL Server 阻止了对组件 'Ad Hoc Distributed Queries' 的 STATEMENT'OpenRowset/OpenDatasource' 的访问的方法

    1.开启Ad Hoc Distributed Queries组件,在sql查询编辑器中执行如下语句: reconfigure reconfigure 2.关闭Ad Hoc Distributed Qu ...

  5. C#基础系列:开发自己的窗体设计器(PropertyGrid显示中文属性名)

    既然是一个窗体设计器,那就应该能够设置控件的属性,设置属性最好的当然是PropertyGrid了,我们仅仅需要使用一个PropertyGrid.SelectedObject = Control就可以搞 ...

  6. 如何配置CentOS或者RedHat5.X、6.X、7.X的网络yum源

    第一步:找到一个可靠的yum源 中科大帮助:https://lug.ustc.edu.cn/wiki/mirrors/help/centos源:http://mirrors.ustc.edu.cn/c ...

  7. WindowsService(Windows服务)开发步骤附Demo

    1.打开VS,新建项目,选择Windows服务,然后设置目录及项目名称后点击确定. 2.展开Service1服务文件,编写service1.cs类文件,不是Service1[设计].然后修改OnSta ...

  8. Linux内核分析——期末总结

    Linux内核学习总结 首先非常感谢网易云课堂这个平台,让我能够在课下学习,课上加强,体会翻转课堂的乐趣.孟宁老师的课程循序渐进,虽然偶尔我学习地不是很透彻,但能够在后续的课程中进一步巩固学习,更加深 ...

  9. 8.HBase In Action 第一章-HBase简介(1.2.2 捕获增量数据)

    Data often trickles in and is added to an existing data store for further usage, such as analytics, ...

  10. Slider 滚动条 Pagination分页插件 JS Ajax 数据范围筛选 加载 翻页 笔记

    入职以后的第二个任务  根据用户所选的价格范围 筛选数据 修复BUG - 筛选数据后 总数没有更新.列表显示错误.翻页加载错误 用到的一些知识点 jquery插件系列之 - Slider滑块 max ...