Linux内核通知链notifier
1.内核通知链表简介(引用网络资料)
大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣。为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子系统,Linux内核提供了通知链的机制。通知链表只能够在内核的子系统之间使用,而不能够在内核与用户空间之间进行事件的通知。
通知链表是一个函数链表,链表上的每一个节点都注册了一个函数。当某个事情发生时,链表上所有节点对应的函数就会被执行。所以对于通知链表来说有一个通知方与一个接收方。在通知这个事件时所运行的函数由被通知方决定,实际上也即是被通知方注册了某个函数,在发生某个事件时这些函数就得到执行。其实和系统调用signal的思想差不多。
通知链技术可以概括为:事件的被通知者将事件发生时应该执行的操作通过函数指针方式保存在链表(通知链)中,然后当事件发生时通知者依次执行链表中每一个元素的回调函数完成通知。
2.内核通知链表数据结构
通知链表的节点类型为notifier_block,其定义如下:
struct notifier_block {
int (*notifier_call)(struct notifier_block *, unsigned long, void *);
struct notifier_block *next;
int priority;
};
notifier_call:该节点所对应的要运行的函数。
*next:指向下一个节点,事件发生时,依次执行的
3.内核通知链注册函数:
在通知链注册时,需要有一个链表头,它指向这个通知链表的第一个元素。这样,之后的事件对该链表通知时就会根据这个链表头而找到这个链表中所有的元素。链表头的定义见本文第6节。
注册的函数是:
/*
*
Notifier chain core routines.
The exported routines below
*
are layered on top of these, with appropriate locking added.
*/
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;
}
从上面的函数实现来看,被通知者调用 notifier_chain_register 函数注册回调函数,该函数是按照优先级将回调函数加入到通知链中去的。
卸载的函数是:
static int 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;
}
将节点n从nl所指向的链表中删除。
在kernel/notifier.c中内核根据通知链的类型分别包装了下面这几个函数:
int atomic_notifier_chain_register(struct atomic_notifier_head *nh,
struct notifier_block *n)
int blocking_notifier_chain_register(struct blocking_notifier_head *nh,
struct notifier_block *n)
int raw_notifier_chain_register(struct raw_notifier_head *nh,
struct notifier_block *n)
int srcu_notifier_chain_register(struct srcu_notifier_head *nh,
struct notifier_block *n)
4.内核通知链通知函数:
当有事件发生时,通知者调用 notifier_call_chain 函数通知事件的到达,这个函数会遍历n1指向的通知链中所有的元素,然后依次调用每一个的回调函数,完成通知动作。
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_raw(*nl);
while (nb && nr_to_call) {
next_nb = rcu_dereference_raw(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;
}
在kernel/notifier.c中内核根据通知链的类型分别包装了上面这个函数:
int atomic_notifier_call_chain(struct atomic_notifier_head *nh,
unsigned long val, void *v)
int blocking_notifier_call_chain(struct blocking_notifier_head *nh,
unsigned long val, void *v)
int raw_notifier_call_chain(struct raw_notifier_head *nh,
unsigned long val, void *v)
int srcu_notifier_call_chain(struct srcu_notifier_head *nh,
unsigned long val, void *v)
5.通知链四种类型
(5.1)原子通知链的链头:
通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,不允许阻塞。
struct atomic_notifier_head {
spinlock_t lock;
struct notifier_block *head;
};
(5.2)可阻塞通知链:
通知链元素的回调函数在进程上下文中运行,允许阻塞。
struct blocking_notifier_head {
struct rw_semaphore rwsem;
struct notifier_block *head;
};
(5.3)原始通知链:
对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。
struct raw_notifier_head {
struct notifier_block *head;
};
(5.4)SRCU 通知链:
可阻塞通知链的变种。
struct srcu_notifier_head {
struct mutex mutex;
struct srcu_struct srcu;
struct notifier_block *head;
};
(6)定义一个通知链的头部结点并初始化:
在include/linux/Notifier.h中
初始化宏定义:
#define ATOMIC_NOTIFIER_INIT(name) {
.lock = __SPIN_LOCK_UNLOCKED(name.lock),
.head = NULL }
#define BLOCKING_NOTIFIER_INIT(name) {
.rwsem = __RWSEM_INITIALIZER((name).rwsem),
.head = NULL }
#define RAW_NOTIFIER_INIT(name)
{
.head = NULL }
/* srcu_notifier_heads cannot be initialized statically */
定义通知链:
#define ATOMIC_NOTIFIER_HEAD(name)
struct atomic_notifier_head name =
ATOMIC_NOTIFIER_INIT(name)
#define BLOCKING_NOTIFIER_HEAD(name)
struct blocking_notifier_head name =
BLOCKING_NOTIFIER_INIT(name)
#define RAW_NOTIFIER_HEAD(name)
struct raw_notifier_head name =
RAW_NOTIFIER_INIT(name)