刚才说过,通知链的原型就是一个单向链表,内核提供的通知链机制主要用于不同子系统之间通信,基于事件和优先级。往通俗里将,考虑这么一种场景:对于网卡驱动子系统来说,经常会发生的情况就是什么?网卡IP地址有变化,网卡状态有变化等等。那么如果有其他子系统,比如路由子系统最网卡IP地址变化这件事比较感兴趣,它该怎么去感知这件事儿呢?当然这种场景下,很多人第一直觉就是“订阅者-发布者”模型。不过确实是这样的,通知链机制可以算作是“订阅者-发布者”模型的一种。每个子系统都会有些一些重要事件,例如前面说的,网络驱动子系统网卡的事件,或者USB的状态事件等等,这些子系统都会提供一个自己的事件队列,这个队列都是其他函数提供的回调函数。当有事件发生时,子系统就会去遍历其事件队列上已经注册了的所有回调函数,这样就实现了“通知”的目的。说的云里雾里的,还是看图吧:
内核里通知链队列里,每个元素都是一个通知块,原型如下:
点击(此处)折叠或打开
-
/* include/linux/notifier.h*/
-
struct notifier_block {
-
int (*notifier_call)(struct notifier_block *, unsigned long, void *);
-
struct notifier_block *next;
-
int priority;
- };
notifier_call是回调函数的指针,指向的函数是当事件发生时要执行的函数;next指向下一个回调函数的通知块;priority是事件发生时本函数(由notifier_call所指向)执行的优先级,数字越小优先级越高,越会先被执行。我们看到这个通知块的结构并不复杂,甚至可以说是已经非常简单明了,每一个这样的通知块串起来就是我们所说的通知链了。
Linux内核提供了三类通知链:原子通知链、阻塞通知链和原始通知链,它们的主要区别就是在执行通知链上的回调函数时是否有安全保护措施。下面我们分别看一下这三类通知链:
1、原子通知链(Atomic Notifier Chains)
原子通知链的链表头定义如下:
点击(此处)折叠或打开
-
struct atomic_notifier_head {
-
spinlock_t lock;
-
struct notifier_block *head;
- };
2、可阻塞通知链(Blocking Notifier Chains)
可阻塞的通知链有两种类型,一种用信号量实现回调函数的加锁,另一种是采用互斥锁和叫做“可睡眠的读拷贝更新机制”(Sleepable Read-Copy UpdateSleepable Read-Copy Update),链表头的定义分别如下:
点击(此处)折叠或打开
-
struct blocking_notifier_head {
-
struct rw_semaphore rwsem;
-
struct notifier_block *head;
- };
点击(此处)折叠或打开
-
struct srcu_notifier_head {
-
struct mutex mutex;
-
struct srcu_struct srcu;
-
struct notifier_block *head;
- };
3、原始通知链(Raw Notifier Chains)
顾名思义,没有任何安保措施,对链表的加锁和保护全部由调用者自己实现,定义如下:
点击(此处)折叠或打开
-
struct raw_notifier_head {
-
struct notifier_block *head;
- };
这三类通知链,我们该怎么用这才是我需要关心的问题。在定义自己的通知链的时候,心里必须明确,自己需要一个什么样类型的通知链,是原子的、可阻塞的还是一个原始通知链。内核中用于定义并初始化不同类通知链的函数分别是:
点击(此处)折叠或打开
- ATOMIC_NOTIFIER_HEAD(name) //定义并初始化一个名为name的原子通知链
- BLOCKING_NOTIFIER_HEAD(name) //定义并初始化一个名为name的阻塞通知链
- RAW_NOTIFIER_HEAD(name) //定义并初始化一个名为name的原始通知链
点击(此处)折叠或打开
- struct atomic_notifier_head mynotifierlist =
-
{
-
.lock = __SPIN_LOCK_UNLOCKED(mynotifierlist.lock),
- .head = NULL
- }
另外两个接口也类似。如果我们已经有一个通知链的对象,Linux还提供了一组用于初始化一个通知链对象的API:
点击(此处)折叠或打开
- ATOMIC_INIT_NOTIFIER_HEAD(name)
- BLOCKING_INIT_NOTIFIER_HEAD(name)
- RAW_INIT_NOTIFIER_HEAD(name)
这一组接口一般在下列格式的代码里见到的会比较多一点:
点击(此处)折叠或打开
-
static struct atomic_notifier_head dock_notifier_list;
- ATOMIC_INIT_NOTIFIER_HEAD(&dock_notifier_list);
OK,有了通知链只是第一步,接下来我们还需要提供往通知链上注册通知块、卸载通知块、已经遍历执行通知链上每个通知块里回调函数的基本接口,说白了就是单向链表的插入、删除和遍历,这样理解就可以了。
内核提供最基本的通知链的常用接口如下:
点击(此处)折叠或打开
- static int notifier_chain_register(struct notifier_block **nl, struct notifier_block *n);
- static int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n);
- static int __kprobes notifier_call_chain(struct notifier_block **nl, unsigned long val, void *v, int nr_to_call, int *nr_calls);
这最基本的三个接口分别实现了对通知链上通知块的注册、卸载和遍历操作,可以想象,原子通知链、可阻塞通知链和原始通知链一定会对基本通知链的操作函数再进行一次包装的,事实也确实如此:
点击(此处)折叠或打开
-
//原子通知链
- int atomic_notifier_chain_register(struct atomic_notifier_head *nh, struct notifier_block *nb);
- int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh, struct notifier_block *nb);
-
int atomic_notifier_call_chain(struct atomic_notifier_head *nh, unsigned long val, void *v);
-
- //可阻塞通知链
- int blocking_notifier_chain_register(struct blocking_notifier_head *nh, struct notifier_block *nb);
- int blocking_notifier_chain_cond_register(struct blocking_notifier_head *nh, struct notifier_block *nb);
- int srcu_notifier_chain_register(struct srcu_notifier_head *nh, struct notifier_block *nb);
-
- int blocking_notifier_call_chain(struct blocking_notifier_head *nh,unsigned long val, void *v);
-
int srcu_notifier_call_chain(struct srcu_notifier_head *nh, unsigned long val, void *v);
-
- int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh, struct notifier_block *nb);
-
int srcu_notifier_chain_unregister(struct srcu_notifier_head *nh, struct notifier_block *nb);
- //原始通知链
- int raw_notifier_chain_register(struct raw_notifier_head *nh, struct notifier_block *nb);
- int raw_notifier_chain_unregister(struct raw_notifier_head *nh,struct notifier_block *nb);
- int raw_notifier_call_chain(struct raw_notifier_head *nh, unsigned long val, void *v);
上述这三类通知链的基本API又构成了内核中其他子系统定义、操作自己通知链的基础。例如,Netlink定义了一个原子通知链,所以,它对原子通知链的基本API又封装了一层,以形成自己的特色:
点击(此处)折叠或打开
-
/*net/netlink/af_netlink.c*/
- ...
-
static ATOMIC_NOTIFIER_HEAD(netlink_chain);
- ...
-
int netlink_register_notifier(struct notifier_block *nb)
-
{
-
return atomic_notifier_chain_register(&netlink_chain, nb);
-
}
- ...
-
-
int netlink_unregister_notifier(struct notifier_block *nb)
-
{
-
return atomic_notifier_chain_unregister(&netlink_chain, nb);
- }
- ...
网络事件也有一个原子通知链:
点击(此处)折叠或打开
-
/*net/core/netevent.c*/
-
/*
-
* Network event notifiers
-
*
-
* Authors:
-
* Tom Tucker <tom@opengridcomputing.com>
-
* Steve Wise <swise@opengridcomputing.com>
-
*
-
* This program is free software; you can redistribute it and/or
-
* modify it under the terms of the GNU General Public License
-
* as published by the Free Software Foundation; either version
-
* 2 of the License, or (at your option) any later version.
-
*
-
* Fixes:
-
*/
-
-
#include <linux/rtnetlink.h>
-
#include <linux/notifier.h>
-
#include <net/netevent.h>
-
-
static ATOMIC_NOTIFIER_HEAD(netevent_notif_chain);
-
-
/**
-
* register_netevent_notifier - register a netevent notifier block
-
* @nb: notifier
-
*
-
* Register a notifier to be called when a netevent occurs.
-
* The notifier passed is linked into the kernel structures and must
-
* not be reused until it has been unregistered. A negative errno code
-
* is returned on a failure.
-
*/
-
int register_netevent_notifier(struct notifier_block *nb)
-
{
-
int err;
-
-
err = atomic_notifier_chain_register(&netevent_notif_chain, nb);
-
return err;
-
}
-
-
/**
-
* netevent_unregister_notifier - unregister a netevent notifier block
-
* @nb: notifier
-
*
-
* Unregister a notifier previously registered by
-
* register_neigh_notifier(). The notifier is unlinked into the
-
* kernel structures and may then be reused. A negative errno code
-
* is returned on a failure.
-
*/
-
-
int unregister_netevent_notifier(struct notifier_block *nb)
-
{
-
return atomic_notifier_chain_unregister(&netevent_notif_chain, nb);
-
}
-
-
/**
-
* call_netevent_notifiers - call all netevent notifier blocks
-
* @val: value passed unmodified to notifier function
-
* @v: pointer passed unmodified to notifier function
-
*
-
* Call all neighbour notifier blocks. Parameters and return value
-
* are as for notifier_call_chain().
-
*/
-
-
int call_netevent_notifiers(unsigned long val, void *v)
-
{
-
return atomic_notifier_call_chain(&netevent_notif_chain, val, v);
-
}
-
-
EXPORT_SYMBOL_GPL(register_netevent_notifier);
-
EXPORT_SYMBOL_GPL(unregister_netevent_notifier);
- EXPORT_SYMBOL_GPL(call_netevent_notifiers)
未完,待续...