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)
下面分析下内存屏障在RCU上的应用:
#define rcu_assign_pointer(p, v) ({ \
smp_wmb();\
(p)= (v); \
})
#define rcu_dereference(p) ({ \
typeof(p)_________p1 = p; \
smp_read_barrier_depends();\
(_________p1);\
})
rcu_assign_pointer()通常用于写者的发布,rcu_dereference()通常用于读者的订阅。
写者:
1 p->a = 1;
2 p->b = 2;
3 p->c = 3;
4 rcu_assign_pointer(gp, p);
读者:
1 rcu_read_lock();
2 p = rcu_dereference(gp);
3 if (p != NULL) {
4 do_something_with(p->a, p->b, p->c);
5 }
6 rcu_read_unlock();
rcu_assign_pointer()是说,先把那块内存写好,再把指针指过去。这里使用的内存写屏障是为了保证并发的读者读到数据一致性。在这条语句之前的读者读到旧的指针和旧的内存,这条语句之后的读者读到新的指针和新的内存。如果没有这条语句,很有可能出现读者读到新的指针和旧的内存。也就是说,这里通过内存屏障刷新了p所指向的内存的值,至于gp本身的值有没有更新还不确定。实际上,gp本身值的真正更新要等到并发的读者来促发。
rcu_dereference() 原语用的是数据依赖屏障,smp_read_barrier_dependence,它要求后面的读操作如果依赖前面的读操作,则前面的读操作需要首先完成。根据数据之间的依赖,要读p->a, p->b, p->c, 就必须先读p,要先读p,就必须先读p1,要先读p1,就必须先读gp。也就是说读者所在的core在进行后续的操作之前,gp必须是同步过的当前时刻的最新值。如果没有这个数据依赖屏障,有可能读者所在的core很长一段时间内一直用的是旧的gp值。所以,这里使用数据依赖屏障是为了督促写者将gp值准备好,是为了呼应写者,这个呼应的诉求是通过数据之间的依赖关系来促发的,也就是说到了非呼应不可的地步了。
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)
阅读(1525) | 评论(0) | 转发(0) |