我是靠谱客的博主 落后飞机,最近开发中收集的这篇文章主要介绍Linux内核通知链,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

1、通知链简介

内核模块之间、内核与驱动、驱动与驱动之间等等时长需要一种通知手段,用于在处理特定事物时,通知相关模块。比如:

/*
*
Notifier list for kernel code which wants to be called
*
at shutdown. This is used to stop any idling DMA operations
*
and the like.
*/
BLOCKING_NOTIFIER_HEAD(reboot_notifier_list);

在shutdown的时候,通知驱动停止DMA操作等等;非常容易理解,很类似于我们生活中的这种场景:

小美:小明,你下班的时候帮我把我的电脑关掉。
...
终于要下班了,小明把小美的电脑关掉,愉快的回家了。

内核中提供了这样一种通用的方法,用于不同的通知,比如reboot、oom、uce、die等等,当然开发者也可以定义自己的通知。

2、数据结构

实现并不复杂,先从数据结构看起,理解整个逻辑的流程使用到得数据是如何组织的

typedef int (*notifier_fn_t)(struct notifier_block *nb, unsigned long action, void *data);
struct notifier_block *nb:执行时,将包含该回调的struct notifier结构传递进来;
unsigned long action/void *data:由特定通知的定义者决定传递给回调的一些信息;
struct notifier_block {
notifier_fn_t notifier_call;
struct notifier_block __rcu *next;
int priority;
};

notifier_fn_t notifier_call:回调函数,也就是在特定事物处理时对其他模块的通知,具体通知到其他模块做什么,就是在这个回调函数中处理,因此这是由其他模块指定的(需要注册)。
struct notifer_block __rcu *next:将需要的通知串联起来,在需要时逐个通知。
int priority:通知的优先级,根据优先级决定通知的先后。

内核根据不同的使用场景,提供了四种类型的通知链.

1、Atomic notifier chains: Chain callbacks run in interrupt/atomic context. Callouts are not allowed to block.
struct atomic_notifier_head {
spinlock_t lock;
struct notifier_block __rcu *head;
};
2、Blocking notifier chains: Chain callbacks run in process context. Callouts are allowed to block.
struct blocking_notifier_head {
struct rw_semaphore rwsem;
struct notifier_block __rcu *head;
};
3、Raw notifier chains: There are no restrictions on callbacks, registration, or unregistration.
All locking and protection must be provided by the caller.
struct raw_notifier_head {
struct notifier_block __rcu *head;
};
4、SRCU notifier chains: A variant of blocking notifier chains, with the same restrictions.
struct srcu_notifier_head {
struct mutex mutex;
struct srcu_struct srcu;
struct notifier_block __rcu *head;
};

3、相关函数

相关的函数主要涉及这几个方面:定义一个通知链、通知链上注册/去注册一个通知、执行通知,同样的不同的场景使用不同通知链时有不同的函数。
对于不同类型的通知链,notifier.c中给出了如下的注解:

* atomic_notifier_chain_register() may be called from an atomic context,
* but blocking_notifier_chain_register() and srcu_notifier_chain_register()
* must be called from a process context.
Ditto for the corresponding
* _unregister() routines.
*
* atomic_notifier_chain_unregister(), blocking_notifier_chain_unregister(),
* and srcu_notifier_chain_unregister() _must not_ be called from within
* the call chain.
*
* SRCU notifier chains are an alternative form of blocking notifier chains.
* They use SRCU (Sleepable Read-Copy Update) instead of rw-semaphores for
* protection of the chain links. This means there is _very_ low overhead
* in srcu_notifier_call_chain(): no cache bounces and no memory barriers.
* As compensation, srcu_notifier_chain_unregister() is rather expensive.
* SRCU notifier chains should be used when the chain will be called very
* often but notifier_blocks will seldom be removed.
定义通知链以及相应的初始化函数:
#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_resem(&(name)->rwsem); 
(name)->head = NULL; 
} while (0)
#define RAW_INIT_NOTIFIER_HEAD(name) do {

(name)->head = NULL; 
} while (0)
void srcu_init_notifier_head(struct srcu_notifier_head *nh)
{
mutex_init(&nh->mutex);
if (init_srcu_struct(&nh->srcu) < 0)
BUG();
nh->head = NULL;
}
或者在定义时及初始化,下面以struct atomic_notifier_head为例:
#define ATOMIC_NOTIFIER_INIT(name) {

.lock = __SPIN_LOCK_UNLOCKED(name.lock),

.head = NULL }
#define ATOMIC_NOTIFIER_HEAD(name)

struct atomic_notifier_head name =

ATOMIC_NOTIFIER_INIT(name)

注册/去注册通知函数,以struct atomic_notifier_head为例,其他的参考代码,不在说明:

int notifier_chain_register(struct notifier_block **nl, struct notifier_block *n)
{
while ((*nl) != NULL) {
if (n->proority > (*nl)->priority)
break;
nl = &((*nl)->next);
}
n->next = *nl;
/* 按照高优先级,后到的排在前面 */
rcu_assign_pointer(*nl, n);
}
int atomic_notifier_chain_register(struct atomic_notifier_head *nh, struct notifier_block *n)
{
unsigned long flags;
int ret;
spin_lock_irqsave(&nh->lock, flags);
ret = notifier_chain_register(&nh->head, n);
spin_unlock_irqrestore(&nh->lock, flags);
return ret;
}
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;
}
int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh, struct notifier_block *n)
{
unsigned long flags;
int ret;
spin_lock_irqsave(&nh->lock, flags);
ret = notifier_chain_unregister(&nh->head, n);
spin_unlock_irqrestore(&nh->lock, flags);
synchronize_rcu();
return ret;
}

执行通知,同样以struct atomic_notifier_head为例:

/**
* 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 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);
ret = nb->notifier_call(nb, val, v);
if (nr_calls)
(*nr_calls)++;
if (ret & NOTIFY_STOP_MASK)
break;
nb = next_nb;
nr_to_call--;
}
return ret;
}
/**
*
__atomic_notifier_call_chain - Call functions in an atomic notifier chain
*
@nh: Pointer to head of the atomic notifier chain
*
@val: Value passed unmodified to notifier function
*
@v: Pointer passed unmodified to notifier function
*
@nr_to_call: See the comment for notifier_call_chain.
*
@nr_calls: See the comment for notifier_call_chain.
*
*
Calls each function in a notifier chain in turn.
The functions
*
run in an atomic context, so they must not block.
*
This routine uses RCU to synchronize with changes to the chain.
*
*
If the return value of the notifier can be and'ed
*
with %NOTIFY_STOP_MASK then atomic_notifier_call_chain()
*
will return immediately, with the return value of
*
the notifier function which halted execution.
*
Otherwise the return value is the return value
*
of the last notifier function called.
*/
int __atomic_notifier_call_chain(struct atomic_notifier_head *nh,
unsigned long val, void *v,
int nr_to_call, int *nr_calls)
{
int ret;
rcu_read_lock();
ret = notifier_call_chain(&nh->head, val, v, nr_to_call, nr_calls);
rcu_read_unlock();
return ret;
}
int atomic_notifier_call_chain(struct atomic_notifier_head *nh,
unsigned long val, void *v)
{
return __atomic_notifier_call_chain(nh, val, v, -1, NULL);
}

4、实例

以panic_notifier_list为例,看下整个通知链的使用:

定义并初始化panic通知链:
ATOMIC_NOTIFIER_HEAD(panic_notifier_list);
注册一个通知:
static int __init hung_task_init(void)
{
atomic_notifier_chain_register(&panic_notifier_list, &panic_block);
...
}
static int hung_task_panic(struct notifier_block *this, unsigned long event, void *ptr)
{
did_panic = 1;
return NOTIFY_DONE;
}
static struct notifier_block panic_block = {
.notifier_call = hung_task_panic,
};
执行通知:
void panic(const char *fmt, ...)
{
...
/*
* Run any panic handlers, including those that might need to
* add information to the kmsg dump output.
*/
atomic_notifier_call_chain(&panic_notifier_list, 0, buf);
...
}

【欢迎一起交流学习】

最后

以上就是落后飞机为你收集整理的Linux内核通知链的全部内容,希望文章能够帮你解决Linux内核通知链所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(44)

评论列表共有 0 条评论

立即
投稿
返回
顶部