os-内核通知链notifier.c
8. linux内核通知链
8.1. 概述
在Linux内核中,各个子系统之间有很强的相互关系,某些子系统可能对其它子系统产生的事件感兴趣。为了让某个子系统在发生某个事件时通知感兴趣的子系统,Linux内核引入了通知链技术。通知链只能够在内核的子系统之间使用,而不能够在内核和用户空间进行事件的通知。
组成内核的核心系统代码均位于kernel目录下,通知链表就位于其中,它位于 kernel/notifier.c
中,对应的头文件为 include/linux/notifier.h
。
从技术上来讲,这并不是一个多么复杂、高深、难懂的部分,说白了就是一个单向链表的插入、删除和遍历等操作。实现她的代码不超过1000行。
8.2. 数据结构
所有通知链的核心数据结构都位于 notifier.h
中。通知链的核心结构是 notifier_block
。
struct notifier_block {
notifier_fn_t notifier_call;
struct notifier_block __rcu *next;
int priority;
};
其中 notifier_call
是通知链要执行的函数指针, next
用来连接其它的通知结构, priority
是这个通知的优先级,同一条链上的 notifier_block
是按优先级排列的,数字越大,优先级越高,越会被先执行。
内核代码中一般把通知链命名为 xxx_chain
, xxx_nofitier_chain
这种形式的变量名。围绕核心数据结构 notifier_block
,内核定义了四种通知链类型,它们的主要区别就是在执行通知链上的回调函数时是否有安全保护措施:
- 原子通知链( Atomic notifier chains ):原子通知链采用的自旋锁,通知链元素的回调函数(当事件发生时要执行的函数)在中断或原子操作上下文中运行,不允许阻塞。对应的链表头结构 :
struct atomic_notifier_head {
spinlock_t lock;
struct notifier_block __rcu *head;
};
- 可阻塞通知链( Blocking notifier chains ):可阻塞通知链使用信号量实现回调函数的加锁,通知链元素的回调函数在进程上下文中运行,允许阻塞。对应的链表头 :
struct blocking_notifier_head {
struct rw_semaphore rwsem;
struct notifier_block __rcu *head;
};
- 原始通知链( Raw notifier chains ):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。对应的链表头 :
struct raw_notifier_head {
struct notifier_block __rcu *head;
};
- SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体,采用互斥锁和叫做 可睡眠的读拷贝更新机制 (Sleepable Read-Copy UpdateSleepable Read-Copy Update)。对应的链表头 :
struct srcu_notifier_head {
struct mutex mutex;
struct srcu_struct srcu;
struct notifier_block __rcu *head;
};
8.3. 如何使用通知链
这四类通知链,我们该怎么用这才是我需要关心的问题。在定义自己的通知链的时候,心里必须明确,自己需要一个什么样类型的通知链,是原子的、可阻塞的还是一个原始通知链。内核中用于定义并初始化不同类通知链的函数分别是 :
#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)
其实, ATOMIC_NOTIFIER_HEAD(mynofifierlist)
和下面的代码是等价的,展开之后如下:
struct atomic_notifier_head mynofifierlist =
{
.lock = __SPIN_LOCK_UNLOCKED(mynofifierlist.lock),
.head = NULL
}
另外两个接口也类似。如果我们已经有一个通知链的对象,Linux还提供了一组用于初始化一个通知链对象的API:
#define ATOMIC_INIT_NOTIFIER_HEAD(name) do { \
spin_lock_init(&(name)->lock); \
(name)->head = NULL; \
} while (0)
#define BLOCKING_INIT_NOTIFIER_HEAD(name) do { \
init_rwsem(&(name)->rwsem); \
(name)->head = NULL; \
} while (0)
#define RAW_INIT_NOTIFIER_HEAD(name) do { \
(name)->head = NULL; \
} while (0)
这一组接口一般在下列格式的代码里见到的会比较多一点:
static struct atomic_notifier_head dock_notifier_list;
ATOMIC_INIT_NOTIFIER_HEAD(&dock_notifier_list);
有了通知链只是第一步,接下来我们还需要提供往通知链上注册通知块、卸载通知块、已经遍历执行通知链上每个通知块里回调函数的基本接口,说白了就是单向链表的插入、删除和遍历,这样理解就可以了。
内核提供最基本的通知链的常用接口为如下:
static int notifier_chain_register(struct notifier_block **nl,
struct notifier_block *n)
static int notifier_chain_cond_register(struct notifier_block **nl,
struct notifier_block *n)
static int notifier_chain_unregister(struct notifier_block **nl,
struct notifier_block *n)
static int notifier_call_chain(struct notifier_block **nl,
unsigned long val, void *v,
int nr_to_call, int *nr_calls)
这最基本的三个接口分别实现了对通知链上通知块的注册、卸载和遍历操作,可以想象,原子通知链、可阻塞通知链和原始通知链一定会对基本通知链的操作函数再进行一次包装的,事实也确实如此:
// 注册函数
extern int atomic_notifier_chain_register(struct atomic_notifier_head *nh,
struct notifier_block *nb);
extern int blocking_notifier_chain_register(struct blocking_notifier_head *nh,
struct notifier_block *nb);
extern int raw_notifier_chain_register(struct raw_notifier_head *nh,
struct notifier_block *nb);
extern int srcu_notifier_chain_register(struct srcu_notifier_head *nh,
struct notifier_block *nb);
extern int blocking_notifier_chain_cond_register(
struct blocking_notifier_head *nh,
struct notifier_block *nb);
// 卸载函数
extern int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh,
struct notifier_block *nb);
extern int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh,
struct notifier_block *nb);
extern int raw_notifier_chain_unregister(struct raw_notifier_head *nh,
struct notifier_block *nb);
extern int srcu_notifier_chain_unregister(struct srcu_notifier_head *nh,
struct notifier_block *nb);
// 遍历操作
extern int atomic_notifier_call_chain(struct atomic_notifier_head *nh,
unsigned long val, void *v);
extern int __atomic_notifier_call_chain(struct atomic_notifier_head *nh,
unsigned long val, void *v, int nr_to_call, int *nr_calls);
extern int blocking_notifier_call_chain(struct blocking_notifier_head *nh,
unsigned long val, void *v);
extern int __blocking_notifier_call_chain(struct blocking_notifier_head *nh,
unsigned long val, void *v, int nr_to_call, int *nr_calls);
extern int raw_notifier_call_chain(struct raw_notifier_head *nh,
unsigned long val, void *v);
extern int __raw_notifier_call_chain(struct raw_notifier_head *nh,
unsigned long val, void *