1 概念:在内核中,有很多的模块,这些模块之间是相互独立的,也有可能某个模块会对其他模块的某个事件感兴趣,这时候就需要将两个模块进行关联,让这两个模块进行通信。所以在linux内核中提供了通知链机制,通知只能用在内核模块之间,不可用在内核和应用空间进行事件的通信。通知链是一个函数链表,链表上的每一个节点都注册了一个函数,当某个事件发生时,链表上的所有节点都会被通知,所以,在通知事件时,所运行的函数由被通知方决定。
2 数据结构:内核中的通知链有四种类型
①原子通知链:通知链节点的回调函数(当事件发生时要执行的处理事件的函数)只能运行在中断上下文中,不允许阻塞。
struct automic notifier_head
{
spinlock_tlock;
struct notifer_block *head;
}
②可阻塞通知链:通知链节点的回掉函数运行在进程上下文中,允许阻塞
struct blocking_notifier_head
{
struct rw_semaphore rwsem;
struct notifier_block *head;
};
③原始通知链( Raw notifier chains ):对通知链节点的回调函数没有任何限制,所有锁和保护机制都由调用者维护。对应的链表头:
struct raw_notifier_head
{
struct notifier_block *head;
};
④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指针形成了通知链节点的“队列”。next用来连接其它的通知结构,priority是这个通知的优先级,同一条链上的notifier_block{}是按优先级排列的。内核代码中一般把通知链命名为xxx_chain, xxx_nofitier_chain这种形的变量名。
3 机制
linux内核的通知链中,主要分为两个角色,通知者和被通知者。
通知者:事件的通知者。当某个是事件产生时,或者通知者自身产生事件时,通知所有对该事件感兴趣的模块。通知者定义了一个通知链,其中保存了每一个被通知者对事件的处理函数的回调(被通知者通过注册函数注册到通知者的通知链上的)
被通知者:对某个事件感兴趣的模块,该模块定义了事件发生时的回掉函数,通过通知者定义的注册函数,将回掉函数注册到通知者的通知链上的。该函数对自己感兴趣的事件进行处理。
通知链的使用主要分为3个步骤,其中通知者有2步,被通知者有1步。
①通知者:定义并初始化通知链,封装通知链的注册函数,注销函数以及通知函数
②被通知者:对事件感兴趣的模块,实现事件的处理函数,该函数中只需对自己感兴趣的事件进行处理,通过通知者提供的注册函数,将该事件处理函数注册到通知者的通知链上
③通知者:事件发生时,使用通知函数来调用被通知者提供的回掉函数,通知被通知者某个事件发生了。
需要注意的是,通知者通知事件时,会通知到注册到该通知链上的所有被通知者,通知时是不区分事件的,所以所有的被通知者都会被通知到,所以被通知者需要对事件进行过滤,只处理自己感兴趣的事件。
4应用举例
使用原始通知链进行举例
①通知者:定义并初始化通知链,提供注册函数,注销函数,通知函数
notifier1.c
#include <linux/kernel.h>
#include <linux/init.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/device.h>
/* 使用这个宏可以定义并初始化通知链test_chain,test_chain是一个通知链的头部节点,后续对通知链的操作都是针对该节点进行的。通过这个节点可以找到注册到这个节点中的其他所有的notifier_block(通过注册函数注册的回掉函数) */
static RAW_NOTIFIER_HEAD(test_chain) ;
/* 自定义注册函数,该函数是封装的原始通知链的注册函数,将前面注册的头节点test_chain作为参数传递进去,这样当其他被通知者调用注册函数进行注册时,被通知者的notifier_block就被注册到这个test_chain通知链上了 */
int register_test_notifier(struct notifier_block *nb)
{
return raw_notifier_chain_register(&test_chain, nb);
}
EXPORT_SYMBOL(register_test_notifier);
/* 注销函数,该函数也是对通知链test_chain进行操作的 */
int unregister_test_notifier(struct notifier_block *nb)
{
return raw_notifier_chain_unregister(&test_chain, nb);
}
EXPORT_SYMBOL(unregister_test_chain);
/* 通知函数,该函数就是当通知者的事件发生时,调用的通知函数,该函数会通过通知链找到所有注册到该通知的回掉函数进行回调。其中的参数val,可以定义成事件类型,回掉函数根据该值进行事件的匹配,参数v可以传递缓存的地址 */
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);
②被通知者
notifier2.c
#include <linux/kernel.h>
#include <linux/init.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/device.h>
extern int register_test_notifier(struct notifier_block *nb);
extern int unregister_test_notifier(struct notifier_block *nb);
/* 实现自己的回掉函数 */
int test_event1(struct notifier_block *nb, unsigned long event, void *ptr)
{
switch(event)
{
case 1:
printk("test event 1\n");
break;
default:
break;
}
return 0;
}
/* 实现自己的回掉函数 */
int test_event2(struct notifier_block *nb, unsigned long event, void *ptr)
{
switch(event)
{
case 2:
printk("test event 2\n");
break;
default:
break;
}
return 0;
}
定义事件的通知块,注册回调函数:
static struct notifier_block test_notifier1 =
{
.notifier_call = test_event1, /* 回调函数 */
}
static struct notifier_block test_notifier2 =
{
.notifier_call = test_event2, /* 回调函数 */
}
/* 被通知者的初始化函数,在该函数中注册通知链 */
int test_notifier_init(void)
{
int iRet;
iRet = register_test_notifier(&test_notifier1);
if(0 != iRet)
{
printk("failed to register notifier 1\n");
}
iRet = register_test_notifier(&test_notifier2);
if(0 != iRet)
{
printk("failed to register notifier 2\n");
}
return 0;
}
int test_notifier_exit(void)
{
unregister_test_notifier(&test_notifier1);
unregister_test_notifier(&test_notifier2);
}
③通知者
notifier3.c
#include <linux/kernel.h>
#include <linux/init.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/device.h>
extern test_notifier_call_chain(unsgined long val,void *v);
在通知者函数中,当事件发生时,直接调用通知函数就可以了。
test_notifier_call_chain(1, NULL);