概 述
内核许多子系统之间关联紧密,因此在一个子系统发生或者检测到的事件信息很可能对其
他子系统来说也是有价值的。为了满足其他子系统对这些事件信息的需求,即在某个子系统内
发生或检测到事件时,其他对此感兴趣的子系统也能知道事件的发生,内核提供了
notification chain机制。
注意:notification chain适用于内核子系统之间的信息传递,不涉及用户态。
Notification chain使用发布-订阅模型(publish-and-subscribe model):
在事件发生时,检测或产生事件的子系统作为主动一方通过通知函数来告知作为被动一方
的订阅者(对此事件感兴趣的子系统)。这里有个额外要求,订阅一方要提供callback函数以
供发布方调用,当然,提供什么样的callback函数完全由订阅方决定。
订阅者必须知道其他子系统提供了哪些事件通知支持,以选择可以订阅的事件通知;当然,订
阅者本身也是一个子系统,因此也具有信息发布功能,因此它也要清楚本系统内哪些事件对其
他子系统是有价值的,即有哪些本系统内的事件发生时需要通知订阅者,但是子系统对谁订阅
了事件通知以及为什么要订阅一无所知。
从某种意义上来说,notification chain实现了事件信息的共享
struct notifier_block结构
Notification chain由notifier block组成,其结构类型如下(include/linux/notifier.h)
:
struct notifier_block
{
int (*notifier_call)(struct notifier_block *self, unsigned long, void *);
struct notifier_block *next;
int priority;
};
notifier_call回调函数:self参数通常为notifier_block本身;unsigned long型参数表示发
生的事件类型,因为一个chain可能支持多个事件,此参数用来对事件进行区分,在
include/linux/notifier.h文件中预订了一些事件常量,例如与netdevice相关的就有多个事件
;void *用来存放私有信息,其具体信息取决于特定的事件
next指针:用于同一个chain中的notifier_block的链接
priority:表示notifier_call函数的优先级,在事件发生时先调用高优先级的回调函数。实际
上在chain中notifier block是根据优先级来进行排队的,高优先级的在前面,这样就可以容易
地实现根据优先级来调用回调函数。同优先级的根据加入chain的顺序来排队,最新加入的排在
同优先级的最后。通常该字段取缺省值0,这样回调函数就根据加入chain的顺序来调用。该字
段的用法可参考函数
notifier_chain_register的实现。
实际上notification chain就是一组函数列表。通常notification chain的名字的格式为
xxx_chain、xxx_notifier_chain、 xxx_notifier_list,例如reboot_notifier_list。
回调函数notifier_call
int (*notifier_call)(struct notifier_block *self, unsigned long, void *);
前面已经讲过函数参数的含义,注意到该函数的返回值为int类型,这里就来看一下可能的返回
值(include/linux/notifier.h文件中定义了这些常值):
NOTIFY_DONE 0x0000
对该事件不感兴趣(根据unsigned long参数)
NOTIFY_OK 0x0001
成功响应该事件
NOTIFY_STOP_MASK 0x8000 该
回调函数返回后停止处理后续notifier block
NOTIFY_BAD (NOTIFY_STOP_MASK|0x0002) 出错,回调函数返回
后停止处理后续notifier block
NOTIFY_STOP (NOTIFY_OK|NOTIFY_STOP_MASK) 成功响应事件,回调函数返
回后停止处理后续notifier block
注意,NOTIFY_STOP和NOTIFY_BAD的定义都包含了NOTIFY_STOP_MASK。
并发访问控制
在kernel/sys.c文件中定义了对notification chain进行并发访问控制的读写锁notifier_lock
。系统中对所有notification chain的并发访问都是由该锁来控制。
子系统通常只在boot或者加载module时注册notifier block,即修改notification chain,而
大多数时间仅以只读方式来访问,因此一个锁基本不会影响系统性能。
基 本 API
下面的基本例程位于kernel/sys.c文件中
要接收某些事件的通知需要先注册到支持这些事件的notification chain中:
int notifier_chain_register(struct notifier_block **list, struct notifier_block *n)
list为notification chain
n为当前子系统提供的notifier_block,其中指明了回调函数
该函数会根据notifier_block的优先级priority将n插入到list中合适位置
如果不想接受已订阅事件的通知,则需要取消订阅注册:
int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n)
nl为notification chain
n为当前子系统提供的notifier_block
当事件发生时,要通知订阅该事件的子系统:
int notifier_call_chain(struct notifier_block **n, unsigned long val, void *v)
n为notification chain
val为事件类型,前面提到过一个chain可能支持多个事件,该参数用来对事件进行区分
v存放特定于事件的信息
该函数会遍历chain,对chain中每个notifier block,以参数val和v调用其notifier_call函数
,若notifier_call函数返回值中标志了NOTIFY_STOP_MASK(如NOTIFY_BAD、NOTIFY_STOP),
则函数停止处理,返回当前notifier block的返回值;否则返回chain中最后一个notifier
block的返回值。
注意:多数子系统都定义了这些基本例程的封装函数,因此很少看到对这些函数的直接调用。
例如后面的例子中用到的register_reboot_notifier和unregister_reboot_notifier就是简单
的封装函数。
简 单 示 例
在kernel/sys.c文件中定义了一个全局reboot_notifier_list,该chain用来挂接想在系统
shutdown时执行的函数,例如进行某种清理工作。前面提到过register_reboot_notifier和
unregister_reboot_notifier是对notifier_chain_register和notifier_chain_unregister简
单封装,具体请参考实现,在此不再赘述。下面给出一个简单的使用notification chain的例子。
该模块向reboot_notifier_list注册了一个函数myreboot,该函数在系统reboot时会简单地打
印一些信息(在系统shutdown时也可以)注意到传递给myreboot的event参数值为1,而include/linux/notifier.h文件中定义了与系统关机相关的一组事件常值,而1对应于事件SYS_DOWN。
#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/notifier.h> static int myreboot(struct notifier_block *self, unsigned long event, void *data) { printk(KERN_ALERT "Just a test! Event code: %li! System reboot now...", event); return NOTIFY_OK; } static struct notifier_block myreboot_notifier = { .notifier_call = myreboot, }; static int myreboot_init(void) { register_reboot_notifier(&myreboot_notifier); return 0; } static void myreboot_exit(void) { unregister_reboot_notifier(&myreboot_notifier); } module_init(myreboot_init); module_exit(myreboot_exit);
|
上述内容原文http://blog.csdn.net/taina2008/archive/2008/10/06/3023724.aspx
本人调试了上述代码,代码在linux2.6.24.4下不能正确编译通过,发现代码中有几个错误和警告。
1.需要引用头文件,这个头文件包含register_reboot_notifier()和unregister_reboot_notifier().
2.最好是加上MODULE_LIENCE("GPL").