Linux Notifier Chains
1. 引言
Linux是单内核架构(monolithic kernel),大多数内核子系统和模块是相互独立的,它们被动态地加载或卸载,以使内核变得小巧和可扩展。然而,子系统或模块之间需要通信,或者说某个特定模块扑捉到的事件可能其它模块对此感兴趣,这就需要一种机制来满足子系统或模块之间交互的需求。
Linux使用通知链表来实现这一需求,它是一个简单的函数链表,当某件事件发生时,链表上的函数就会执行。这是一种发布-订阅(publish-subscribe)模式,当客户(订阅者)需要某个特定事件的通知时,会向主机(发布者)注册自己;接下来,只要感兴趣的事件一发生,主机便会通知客户。
2. Notifier定义
在/include/linux/notifier.h文件中定义了通知链表节点
struct notifier_block {
int (*notifier_call)(struct notifier_block *, unsigned long, void *);
struct notifier_block *next;
int priority;
};
其中,函数指针notifier_call注册了当某个事件发生时需要调用的函数;next指向下一个链表节点;priority设定链表节点的优先级,数值越大优先级越高,默认为0。因此,所有的通知链表节点组成了一个单链表,并以优先级(priority)排列。
3. Notifier类型
内核提供了四种类型的通知链表,它们的分类是基于执行上下文和调用通知链所需的锁保护机制:
·Atomic notifier chains:该通知链表在中断或原子上下文执行,不能阻塞,链表事件对响应时间要求高,定义如下
struct atomic_notifier_head {
spinlock_t lock;
struct notifier_block *head;
};
·Blocking notifier chains:在进程上下文执行,能够阻塞,链表事件对响应时间要求不高,定义如下
struct blocking_notifier_head {
struct rw_semaphore rwsem;
struct notifier_block *head;
};
·Raw notifier chains:调用,注册或卸载链表通知时无限制,所需保护机制由调用者提供,定义如下
struct raw_notifier_head {
struct notifier_block *head;
};
·SRCU notifier chains:这是一种Sleepable Read Copy Update (SRCU)的链表通知,与block链表通知类似,不同在处理锁与保护上,SRCU在调用通知时的系统开销小,而从通知链表中去除通知调用的系统开销大,因此适合用在调用通知频繁,而移除调用通知少的情况中,定义如下
struct srcu_notifier_head {
struct mutex mutex;
struct srcu_struct srcu;
struct notifier_block *head;
};
4. Notifier注册
以blocking notifier chains为例,通常用一个宏来定义并初始化一个通知链表头,代码如下
#define BLOCKING_NOTIFIER_HEAD(name) \
struct blocking_notifier_head name = \
BLOCKING_NOTIFIER_INIT(name)
然后向通知链表中注册通知节点,代码如下
int blocking_notifier_chain_register(struct blocking_notifier_head *nh,
struct notifier_block *n)
{
int ret;
/*
* This code gets used during boot-up, when task switching is
* not yet working and interrupts must remain disabled. At
* such times we must not call down_write().
*/
if (unlikely(system_state == SYSTEM_BOOTING))
return notifier_chain_register(&nh->head, n);
down_write(&nh->rwsem);
ret = notifier_chain_register(&nh->head, n);
up_write(&nh->rwsem);
return ret;
}
第一个参数为通知链表头指针,而第二参数是要注册到该通知链表中的链表节点,调用函数notifier_chain_register( )实现了真正的注册,代码如下
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;
}
当blocking notifier chains的头指针head为NULL时,将所要注册的notifier block
赋给head,而该链表节点的next设为NULL;当blocking notifier chains的头指针head为非空时,若所要注册的notifier block的优先级比头节点的高,则将该链表节点的next指向头节点,而将该链表节点作为新的头节点;当blocking notifier chains的头指针head为非空时,且所要注册的notifier block的优先级比头节点的低,则将依据优先级高低遍历该通知链表上的节点,找到链表上第一个比自身优先级低的节点,将所要注册的链表节点插入到该节点之前。
5. Notifier发送
仍以blocking notifier chains为例,当一个通知链表上注册了链表节点后,需要一个函数去按优先级去激活链表上注册的函数,代码如下
int blocking_notifier_call_chain(struct blocking_notifier_head *nh,
unsigned long val, void *v)
{
return __blocking_notifier_call_chain(nh, val, v, -1, NULL);
}
int __blocking_notifier_call_chain(struct blocking_notifier_head *nh,
unsigned long val, void *v,
int nr_to_call, int *nr_calls)
{
int ret = NOTIFY_DONE;
/*
* We check the head outside the lock, but if this access is
* racy then it does not matter what the result of the test
* is, we re-check the list after having taken the lock anyway:
*/
if (rcu_dereference(nh->head)) {
down_read(&nh->rwsem);
ret = notifier_call_chain(&nh->head, val, v, nr_to_call,
nr_calls);
up_read(&nh->rwsem);
}
return ret;
}
我们发现调用的底层函数是notifier_call_chain( ),代码如下
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(*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;
}
第一个参数传入的是所要通知的链表头指针,第二个和第三个参数是要传递给通知函数的整型和指针参数,第四个参数nr_to_call限定了通知链表上响应的函数个数,为-1时无限制,第五个参数nr_call记录了实际调用的通知链表上的函数个数,为NULL时不记录。
6. Notifier实例应用
以framebuffer子系统为例,简单介绍通知链表的实现。
在/drivers/video/fb_notify.c中初始化了一个名为fb_notifier_list的通知链表头,代码如下
static BLOCKING_NOTIFIER_HEAD(fb_notifier_list);
在/drivers/video/console/fbcon.c文件中,函数fb_console_init( )在初始化framebuffer控制台时调用了函数fb_register_client(&fbcon_event_notifier)向通知链表头fb_notifier_list注册了一个名为fbcon_event_notifier的通知节点,定义如下
static struct notifier_block fbcon_event_notifier = {
.notifier_call = fbcon_event_notify,
};
其中fbcon_event_notify就是通知回调函数;而fb_register_client最终调用的就是注册通知节点的底层函数notifier_chain_register( )。
在/drivers/video/fbmem.c文件中,函数register_framebuffer( )在初始化注册framebuffer驱动时,在所有初始化工作完成后调用了函数fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event)给通知链表fb_notifier_list发送事件FB_EVENT_FB_REGISTERED,表明framebuffer已注册成功了,该函数最终调用了底层的链表通知函数notifier_call_chain( ),此时将遍历通知链表上所用的通知节点,显然节点fbcon_event_notifier的通知函数fbcon_event_notify将最终被调用执行。