欢迎进入Linux社区论坛,与200万技术人员互动交流 >>进入
Linux内核中通知块操作
严禁用于任何商业用途。
msn:
yfydz_no1@hotmail.com
来源:
http://yfydz.cublog.cn
1. 前言
notify是Linux内核中一种常用的事件回调处理机制,提供了基于优先级的回调链表处理功能。
以下内核代码版本为2.6.1Array.2。
2. 数据结构
/* include/linux/notifier.h */
// 基本的通知块结构
struct notifier_block {
// 回调函数
int (*notifier_call)(struct notifier_block *, unsigned long, void *);
// 链表中的下一个结构, 这个一个单向链表
struct notifier_block *next;
// 该块的优先级, 在链表中各个块是按此优先级值进行排序的, 值大的在链表前, 表明
// 相应回调函数执行的顺序
int priority;
};
出来基本的通知块结构, 还定义了一些扩展的通知块结构:
// 原子通知头结构, 增加了一个锁来保证操作的原子性
struct atomic_notifier_head {
spinlock_t lock;
struct notifier_block *head;
};
// 阻塞通知头结构, 增加了一个读写信号灯
struct blocking_notifier_head {
struct rw_semaphore rwsem;
struct notifier_block *head;
};
// 原始通知头结构, 就是一个通知块指针
struct raw_notifier_head {
struct notifier_block *head;
};
// srcu: Sleepable Read-Copy Update mechanism
// srcu通知头结构, 增加了锁和srcu结构
struct srcu_notifier_head {
struct mutex mutex;
struct srcu_struct srcu;
struct notifier_block *head;
};
以下是一些宏来初始化各种类型的通知头结构, 一般在程序中使用:
#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)
以下这些宏也是用来初始化各种类型的通知头结构, 但是在参数定义时使用:
#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 }
注意, 没有定义scru通知头结构的初始化, 因为scru是不能静态初始化的。
以下这些宏用来直接定义通知头结构:
#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)
3. 基本通知块操作函数
关于通知块的基本操作函数都在kernel/sys.c中定义
3.1 登记
该函数将一个通知块结构挂接到指定的通知链表
/*
* Notifier chain core routines. The exported routines below
* are layered on top of these, with appropriate locking added.
*/
// nl是链表头块的地址, n是要添加到该链表的通知块
static int notifier_chain_register(struct notifier_block **nl,
struct notifier_block *n)
{
// 使用的是dummy header算法, 即使刚开始时链表为空也不用显示判断区分
while ((*nl) != NULL) {
// 判断优先权值, 优先权值越大位置越靠前
if (n->priority > (*nl)->priority)
break;
nl = &((*nl)->next);
}
// 将节点n链接到链表nl中的合适位置
n->next = *nl;
// 使用rcu处理函数保证SMP下的安全性, 相当于加上锁再赋值
rcu_assign_pointer(*nl, n);
return 0;
}
3.2 撤销
该函数将一个通知块结构从通知链表中拆除:
// nl是链表头块的地址, n是要删除的通知块
static int notifier_chain_unregister(struct notifier_block **nl,
struct notifier_block *n)
{
while ((*nl) != NULL) {
// 地址匹配, 进行拆除操作
if ((*nl) == n) {
// *nl=n->next的安全赋值操作,相当于将节点从链表断开
rcu_assign_pointer(*nl, n->next);
return 0;
}
nl = &((*nl)->next);
}
return -ENOENT;
}
3.3 回调函数处理
该函数执行链表中各节点的回调函数:
// nl通常是通知块链表头的地址, val和v是传给回调函数的参数
static int __kprobes notifier_call_chain(struct notifier_block **nl,
unsigned long val, void *v)
{
int ret = NOTIFY_DONE;
struct notifier_block *nb, *next_nb;
// 安全地获取通知块指针
nb = rcu_dereference(*nl);
// 链表循环
while (nb) {
// 找下一个块
next_nb = rcu_dereference(nb->next);
// 调用回调函数
ret = nb->notifier_call(nb, val, v);
// 如果返回停止标志, 不执行后续结构
if ((ret & NOTIFY_STOP_MASK) == NOTIFY_STOP_MASK)
break;
// 循环到下一节点
nb = next_nb;
}
return ret;
}
[1][2][3]
即使爬到最高的山上,一次也只能脚踏实地地迈一步。