通知链描述
大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣。为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子系统,Linux内核提供了通知链的机制。通知链表只能够在内核的子系统之间使用,而不能够在内核与用户空间之间进行事件的通知。
通知链表是一个函数链表,链表上的每一个节点都注册了一个函数。当某个事情发生时,链表上所有节点对应的函数就会被执行。所以对于通知链表来说有一个通知方与一个接收方。在通知这个事件时所运行的函数由被通知方决定,实际上也即是被通知方注册了某个函数,在发生某个事件时这些函数就得到执行。
通知链技术可以概括为:事件的被通知者将事件发生时应该执行的操作通过函数指针方式保存在链表(通知链)中,然后当事件发生时通知者依次执行链表中每一个元素的回调函数完成通知。
通知链实现
以下代码来自与linux-3.6.10
/kernel/notifier.c 通知链相关函数的实现
/include/linux/notifier.h 通知链结构、宏等定义
目前代码实现由四种类型的通知链:
原子通知链(Atomic notifier chains):通知链元素回调函数运行在中断或者原子上下文,不允许阻塞
阻塞通知链(Blocking notifier chains):通知链元素回调函数运行在进程上下文,允许阻塞
原始通知链(Raw notifier chains):通知链元素回调函数运行上下文没有限制,所有锁和保护机制都由调用者维护。
SRCU通知链(SRCU notifier chains):一种阻塞通知链的变种,拥有和阻塞通知链同样的限制,与阻塞通知链的区别在于它使用可睡眠的RCU锁(Sleepable Read-Copy Update)而不是读写信号量来保护通知链。也就意味着它执行回调函数(也即srcu_notifier_call_chain())的开销是非常小的,因为不需要缓存反弹和内存屏障。作为代价,srcu_notifier_chain_unregister()的开销是相当大的。所以SRCU通知链适合用在经常通知notifier_blocks在通知链上很少被去注册的情况。
struct notifier_block { int (*notifier_call)(struct notifier_block *, unsigned long, void *); struct notifier_block __rcu *next; int priority; }; struct atomic_notifier_head { spinlock_t lock; struct notifier_block __rcu *head; }; struct blocking_notifier_head { struct rw_semaphore rwsem; struct notifier_block __rcu *head; }; struct raw_notifier_head { struct notifier_block __rcu *head; }; struct srcu_notifier_head { struct mutex mutex; struct srcu_struct srcu; struct notifier_block __rcu *head; };
各种通知链表头的定义
/* srcu_notifier_heads must be initialized and cleaned up dynamically */ extern void srcu_init_notifier_head(struct srcu_notifier_head *nh); #define srcu_cleanup_notifier_head(name) cleanup_srcu_struct(&(name)->srcu); #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)
notifier_chain_register()
添加一个元素到通知链,从notifier_chain_register()实现来看,通知链时按优先级从大到小的有序链表。另外notifier_chain_cond_register()与此不同的是查找是否有相同的通知链元素,如果有则直接返回。
/* * 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_unregister()
从通知链删除一个元素
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; }
notifier_call_chain()
通知已注册模块对应的事件
/** * notifier_call_chain - Informs the registered notifiers about an event. * @nl: Pointer to head of the blocking notifier chain * @val: Value passed unmodified to notifier function * @v: Pointer passed unmodified to notifier function * @nr_to_call: Number of notifier functions to be called. Don't care * value of this parameter is -1. * @nr_calls: Records the number of notifications sent. Don't care * value of this field is NULL. * @returns: notifier_call_chain returns the value returned by the * last notifier function called. */ 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; }
以下函数均是对上面三个函数的封装,只不过同步保护机制不同
atomic_notifier_chain_register()
atomic_notifier_chain_unregister()
atomic_notifier_call_chain()
blocking_notifier_chain_register()
blocking_notifier_chain_cond_register()
blocking_notifier_chain_unregister()
blocking_notifier_call_chain()
raw_notifier_chain_register()
raw_notifier_chain_unregister()
raw_notifier_call_chain()
srcu_notifier_chain_register()
srcu_notifier_chain_unregister()
srcu_notifier_call_chain()
Linux内核通知链模块