Linux内核通知链(notifier chain)是一种机制,用于实现内核中的事件通知和处理。它提供了一种灵活的方式,让不同的模块可以注册自己感兴趣的事件,并在事件发生时接收到通知。
通知链由一个或多个注册在其中的回调函数组成,每个回调函数都有一个优先级。当事件发生时,内核会按照优先级顺序调用相应的回调函数进行处理。
在内核中,常见的使用场景包括:
开发者可以使用notifier_chain_register()函数向通知链中注册回调函数,使用notifier_call_chain()函数触发对通知链中所有回调函数的调用。此外,还可以使用blocking_notifier_chain_register()和blocking_notifier_call_chain()来支持阻塞式操作。
Linux内核提供了三类通知链:原子通知链、阻塞通知链和原始通知链,它们的主要区别就是在执行通知链上的回调函数时是否有安全保护措施。
原子通知链
原子通知链( Atomic notifier chains ):通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,不允许阻塞。对应的链表头结构:
struct atomic_notifier_head
{
spinlock_t lock;
struct notifier_block *head;
};
可阻塞通知链
可阻塞的通知链有两种类型,一种用信号量实现回调函数的加锁,另一种是采用互斥锁和叫做“可睡眠的读拷贝更新机制”(Sleepable Read-Copy UpdateSleepable Read-Copy Update)。
可阻塞型的通知链运行在进程空间的上下文环境里。
可阻塞通知链( Blocking notifier chains ):通知链元素的回调函数在进程上下文中运行,允许阻塞。对应的链表头:
struct blocking_notifier_head
{
struct rw_semaphore rwsem;
struct notifier_block *head;
};
SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体。对应的链表头:
struct srcu_notifier_head
{
struct mutex mutex;
struct srcu_struct srcu;
struct notifier_block *head;
};
原始通知链
原始通知链( Raw notifier chains ):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。对应的链表头:
struct raw_notifier_head
{
struct notifier_block *head;
};
通知链的核心结构:
struct notifier_block
{
int (*notifier_call)(struct notifier_block *, unsigned long, void *);
struct notifier_block *next;
int priority;
};
参数:
最重要的就是notifier_call这个函数指针,代表通知链要执行的函数指针,
next指向下一个回调函数的通知块
priority是这个通知的优先级,同一条链上的notifier_block
是按优先级排列的。priority是事件发生时本函数(由notifier_call所指向)执行的优先级,数字越大优先级越高,越会先被执行。
内核代码中一般把通知链命名为xxx_chain, xxx_nofitier_chain这种形式的变量名。
通知链的运作机制包括两个角色:
包括以下过程:
1、通知者定义通知链。
2、被通知者向通知链中注册回调函数。
3、当事件发生时,通知者发出通知(执行通知链中所有元素的回调函数)。
被通知者调用 notifier_chain_register
函数注册回调函数,该函数按照优先级将回调函数加入到通知链中:
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
函数通知事件的到达,这个函数会遍历通知链中所有的元素,然后依次调用每一个的回调函数(即完成通知动作):
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(*nl);
while (nb && nr_to_call)
{
next_nb = rcu_dereference(nb->next);
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;
}
参数nl是通知链的头部,val表示事件类型,v用来指向通知链上的函数执行时需要用到的参数,一般不同的通知链,参数类型也不一样。
例如当通知一个网卡被注册时,v就指向net_device结构,nr_to_call表示准备最多通知几个,-1表示整条链都通知,nr_calls非空的话,返回通知了多少个。
每个被执行的notifier_block回调函数的返回值可能取值为以下几个:
notifier_call_chain()
把最后一个被调用的回调函数的返回值作为它的返回值。
recommend:
Linux内核源码分析