Linux Notifier Chains
Linux Notifier Chains
1.引言
Linux是单内核架构(monolithic kernel),大多数内核子系统和模块是相互独立的,它们被动态地加载或卸载,以使内核变得小巧和可扩展。然而,子系统或模块之间需要通信,或者说某个特定模块扑捉到的事件可能其它模块对此感兴趣,这就需要一种机制来满足子系统或模块之间交互的需求。
Linux使用通知链表来实现这一需求,它是一个简单的函数链表,当某件事件发生时,链表上的函数就会执行。这是一种发布-订阅(publish-subscribe)模式,当客户(订阅者)需要某个特定事件的通知时,会向主机(发布者)注册自己;接下来,只要感兴趣的事件一发生,主机便会通知客户。
2.Notifier定义
在/include/linux/notifier.h文件中定义了通知链表节点
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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:该通知链表在中断或原子上下文执行,不能阻塞,链表事件对响应时间要求高,定义如下
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
struct atomic_notifier_head { spinlock_t lock; struct notifier_block *head; };
Blocking notifier chains:在进程上下文执行,能够阻塞,链表事件对响应时间要求不高,定义如下
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
struct blocking_notifier_head { struct rw_semaphore rwsem; struct notifier_block *head; };
Raw notifier chains:调用,注册或卸载链表通知时无限制,所需保护机制由调用者提供,定义如下
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
struct raw_notifier_head { struct notifier_block *head; };
SRCU notifier chains:这是一种Sleepable Read Copy Update (SRCU)的链表通知,与block链表通知类似,不同在处理锁与保护上,SRCU在调用通知时的系统开销小,而从通知链表中去除通知调用的系统开销大,因此适合用在调用通知频繁,而移除调用通知少的情况中,定义如下
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
struct srcu_notifier_head { struct mutex mutex; struct srcu_struct srcu; struct notifier_block *head; };
4.Notifier注册
以blocking notifier chains为例,通常用一个宏来定义并初始化一个通知链表头,代码如下'
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#define BLOCKING_NOTIFIER_HEAD(name) \ struct blocking_notifier_head name = \ BLOCKING_NOTIFIER_INIT(name)
然后向通知链表中注册通知节点,代码如下
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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( )实现了真正的注册,代码如下
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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为例,当一个通知链表上注册了链表节点后,需要一个函数去按优先级去激活链表上注册的函数,代码如下
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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( ),代码如下
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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将最终被调用执行。