内核通知链介绍:
在linux内核系统中,各个模块、子系统之间是相互独立的。Linux内核可以通过通知链机制来获取由其它模块或子系统产生的它感兴趣的某些事件。例如,可以通过通知链来获取某总线上的设备增加、删除情况,来动态添加和卸载dev文件设备。
Linux内核中包含四种类型通知链:
1. 原子通知链( Atomic notifier chains):通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,不允许阻塞
struct atomic_notifier_head {
spinlock_t lock;
struct notifier_block __rcu *head;
};
2. 可阻塞通知链( Blocking notifier chains):通知链元素的回调函数在进程上下文中运行,允许阻塞
struct blocking_notifier_head {
struct rw_semaphore rwsem;
struct notifier_block __rcu *head;
};
3. 原始通知链( Raw notifier chains):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护
struct raw_notifier_head {
struct notifier_block __rcu *head;
};
4. SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体
struct srcu_notifier_head {
struct mutex mutex;
struct srcu_struct srcu;
struct notifier_block __rcu *head;
};
通知链可以由structnotifier_block结构来描述
Struct notifier_block{
Int (*nitifier_call)(structnotifier_block *, unsigned long, void *); // 回调函数
Struct notifier_block __rcu *next;
Int priority;
}
以blocking notifier chain为例说明通知链使用:
1.) 定义通知链表头;
Struct blocking_notifier_head bus_notifier;
2.)初始化链表头
BLOCKING_INIT_NOTIFIER_HEAD(&bus_notifier);
3.)定义一个notifier,并增加notifier到通知链
Inti2cdev_notifier_call(struct notifier_block *nb, unsigned long action, void*data)
{
Strcutdevice *dev = data;
Switch(action) {
case BUS_NOTIFY_ADD_DEVICE:
……..
case BUS_NOTIFY_DEL_DEVICE:
…..
}
Return 0;
}
Structnotifier_block i2cdev_notifier = {
.notifier_call =i2cdev_notifier_call,
};
Blocking_notifier_chain_register(&bus_notifier,&i2cdev_notifier);
4.)当所监控的模块或子系统发生对应事件时,调用notifier_call函数通知监控者
Blocking_notifier_call_chain(&bus_notifier,BUS_NOTIFY_ADD_DEVICE, dev);
I2cdev_notifier_call函数被调用:
数据结构
struct notifier_block
{
int (*notifier_call)(struct notifier_block *self, unsigned long, void *); /*回调处理函数*/
struct notifier_block *next;
int priority;
/*用于对注册者进行优先级排队,高优先级的处理例程将被优先执行,由注册者 自己指定 */
};
2。基本例程
1) 先注册到某个notifier_block链;
extern int notifier_chain_register(struct notifier_block **list, struct notifier_block *n);
说明:这时的n可以只要初始化(*notifier_call)指针;
2)当事件发生时,调用notifier_call_chain()来触发相应的处理函数。
extern int notifier_call_chain(struct notifier_block **n, unsigned long val, void *v);
说明:轮循执行某个notifier_block链中的所有notifier_block,对其(*notifier_call)传入参数val和*v;
其中val应该是EVENT NUMBER,而*v是导致这个事件的数据结构,比如某个网络设备UP,则val=NETDEV_UP,v=dev;
3)取消注册
extern int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n);
说明:从某个notifier_block链中移去n;
3返回值
#define NOTIFY_DONE 0x0000 /* Don't care */
#define NOTIFY_OK 0x0001 /* Suits me */
#define NOTIFY_STOP_MASK 0x8000 /* Don't call further */
#define NOTIFY_BAD (NOTIFY_STOP_MASK|0x0002) /* Bad/Veto action */
Notification chain浅析
内 核许多子系统之间关联紧密,因此在一个子系统发生或者检测到的事件信息很可能对其他子系统来说也是有价值的。为了满足其他子系统对这些事件信息的需求,即 在某个子系统内发生或检测到事件时,其他对此感兴趣的子系统也能知道事件的发生,内核提供了notification chain机制。
注意:notification chain适用于内核子系统之间的信息传递,不涉及用户态。
Notification chain使用发布-订阅模型(publish-and-subscribe model):在事件发生时,检测或产生事件的子系统作为主动一方通过通知函数来告知作为被动一方的订阅者(对此事件感兴趣的子系统)。这里有个额外要求,订阅一方要提供callback函数以供发布方调用,当然,提供什么样的callback函数完全由订阅方决定。
订 阅者必须知道其他子系统提供了哪些事件通知支持,以选择可以订阅的事件通知;当然,订阅者本身也是一个子系统,因此也具有信息发布功能,因此它也要清楚本 系统内哪些事件对其他子系统是有价值的,即有哪些本
系统内的事件发生时需要通知订阅者,但是子系统对谁订阅了事件通知以及为什么要订阅一无所知。
从某种意义上来说,notification chain实现了事件信息的共享
notifier_call回调函数:self参数通常为notifier_block本身;unsigned long型参数表示发生的事件类型,因为一个chain可能支持多个事件,此参数用来对事件进行区分,在include/linux/notifier.h文件 中预订了一些
事件常量,例如与netdevice相关的就有多个事件;void *用来存放私有信息,其具体信息取决于特定的事件
next指针:用于同一个chain中的notifier_block的链接
priority: 表示notifier_call函数的优先级,在事件发生时先调用高优先级的回调函数。实际上在chain中notifier block是根据优先级来进行排队的,高优先级的在前面,这样就可以容易地实现根据优先级来调用回调函数
。同优先级的根据加入chain的顺序来排队,最 新加入的排在同优先级的最后。通常该字段取缺省值0,这样回调函数就根据加入chain的顺序来调用。该字段的用法可参考函数 notifier_chain_register的实现。
实际上notification chain就是一组函数列表。通常notification chain的名字的格式为xxx_chain、xxx_notifier_chain、 xxx_notifier_list,例如reboot_notifier_list。
2.1 回调函数notifier_call
int (*notifier_call)(struct notifier_block *self, unsigned long, void *);
前面已经讲过函数参数的含义,注意到该函数的返回值为int类型,这里就来看一下可能的返回值(include/linux/notifier.h文件中定义了这些常值):
NOTIFY_DONE 0x0000 对该事件不感兴趣(根据unsigned long参数)
NOTIFY_OK 0x0001 成功响应该事件
NOTIFY_STOP_MASK 0x8000 该回调函数返回后停止处理后续notifier block
NOTIFY_BAD (NOTIFY_STOP_MASK|0x0002)
出错,回调函数返回后停止处理后续notifier block
NOTIFY_STOP (NOTIFY_OK|NOTIFY_STOP_MASK)
成功响应事件,回调函数返回后停止处理后续notifier block
注意,NOTIFY_STOP和NOTIFY_BAD的定义都包含了NOTIFY_STOP_MASK。
2.2 并发访问控制
在 kernel/sys.c文件中定义了对notification chain进行并发访问控制的读写锁notifier_lock。系统中对所有notification chain的并发访问都是由该锁来控制。子系统通常只在boot或者加载module时注册notifier block
,即修改notification chain,而大多数时间仅以只读方式来访问,因此一个锁基本不会影响系统性能。
3 基 本 例 程
下面的基本例程位于kernel/sys.c文件中
要接收某些事件的通知需要先注册到支持这些事件的notification chain中:
int notifier_chain_register(struct notifier_block **list, struct notifier_block *n)
list为notification chain
n为当前子系统提供的notifier_block,其中指明了回调函数
该函数会根据notifier_block的优先级priority将n插入到list中合适位置
如果不想接受已订阅事件的通知,则需要取消订阅注册:
int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n)
nl为notification chain
n为当前子系统提供的notifier_block
当事件发生时,要通知订阅该事件的子系统:
int notifier_call_chain(struct notifier_block **n, unsigned long val, void *v)
n为notification chain
val为事件类型,前面提到过一个chain可能支持多个事件,该参数用来对事件进行区分
v存放特定于事件的信息
该 函数会遍历chain,对chain中每个notifier block,以参数val和v调用其notifier_call函数,若notifier_call函数返回值中标志了 NOTIFY_STOP_MASK(如NOTIFY_BAD、NOTIFY_STOP),则函数停止处理,返回当前
notifier block的返回值;否则返回chain中最后一个notifier block的返回值。
注意:多数子系统都定义了这些基本例程的封装函数,因此很少看到对这些函数的直接调用。例如后面的例子中用到的register_reboot_notifier和unregister_reboot_notifier就是简单的封装函数。
Linux内核中通知块操作
1. 前言
notify是Linux内核中一种常用的事件回调处理机制,提供了基于优先级的回调链表处理功能。
以下内核代码版本为2.6.19.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;
}
4. 扩展的通知块操作
扩展的通知块操作功能和基本通知块类似, 但使用了扩展的结构中的参数保证操作的安全
4.1 原子通知块
4.1.1 登记
/*
* Atomic notifier chain routines. Registration and unregistration
* use a spinlock, and call_chain is synchronized by RCU (no locks).
*/
/**
* atomic_notifier_chain_register - Add notifier to an atomic notifier chain
* @nh: Pointer to head of the atomic notifier chain
* @n: New entry in notifier chain
*
* Adds a notifier to an atomic notifier chain.
*
* Currently always returns zero.
*/
// 只是在基本通知登记操作前后加锁解锁进行保护
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;
}
EXPORT_SYMBOL_GPL(atomic_notifier_chain_register);
4.1.2 撤销
/**
* atomic_notifier_chain_unregister - Remove notifier from an atomic notifier chain
* @nh: Pointer to head of the atomic notifier chain
* @n: Entry to remove from notifier chain
*
* Removes a notifier from an atomic notifier chain.
*
* Returns zero on success or %-ENOENT on failure.
*/
// 只是在基本通知块撤销操作前后加锁解锁进行保护
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);
// 同步rcu, 等待一个grace period
synchronize_rcu();
return ret;
}
EXPORT_SYMBOL_GPL(atomic_notifier_chain_unregister);
4.1.3 原子回调
这个函数是在原子操作上下文中调用, 是不能阻塞的
int __kprobes atomic_notifier_call_chain(struct atomic_notifier_head *nh,
unsigned long val, void *v)
{
int ret;
// 禁止了抢占
rcu_read_lock();
// 使用基本通知块回调
ret = notifier_call_chain(&nh->head, val, v);
// 允许抢占
rcu_read_unlock();
return ret;
}
EXPORT_SYMBOL_GPL(atomic_notifier_call_chain);
4.2 可阻塞通知块
4.2.1 登记
int blocking_notifier_chain_register(struct blocking_notifier_head *nh,
struct notifier_block *n)
{
int ret;
/*
* This code gets used during boot-up, when task switching is
* not yet working and interrupts must remain disabled. At
* such times we must not call down_write().
*/
// 这是内核启动时就进行调用了, 虽然可能性很小, 直接执行基本登记函数
// 不用处理信号灯, 因为此时是不能阻塞
if (unlikely(system_state == SYSTEM_BOOTING))
return notifier_chain_register(&nh->head, n);
// 使用信号灯进行同步, 可能阻塞
down_write(&nh->rwsem);
// 基本登记函数
ret = notifier_chain_register(&nh->head, n);
up_write(&nh->rwsem);
return ret;
}
EXPORT_SYMBOL_GPL(blocking_notifier_chain_register);
4.2.2 撤销
该函数是在进程处理过程中调用,可阻塞:
int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh,
struct notifier_block *n)
{
int ret;
/*
* This code gets used during boot-up, when task switching is
* not yet working and interrupts must remain disabled. At
* such times we must not call down_write().
*/
// 这是内核启动时就进行调用了, 虽然可能性很小, 直接执行基本撤销函数
// 不用处理信号灯, 因为此时是不能阻塞
if (unlikely(system_state == SYSTEM_BOOTING))
return notifier_chain_unregister(&nh->head, n);
// 使用信号灯进行同步, 可能阻塞
down_write(&nh->rwsem);
// 基本撤销函数
ret = notifier_chain_unregister(&nh->head, n);
up_write(&nh->rwsem);
return ret;
}
EXPORT_SYMBOL_GPL(blocking_notifier_chain_unregister);
4.2.3 回调
在进行上下文中调用, 可以阻塞:
int blocking_notifier_call_chain(struct blocking_notifier_head *nh,
unsigned long val, void *v)
{
int ret;
// 信号灯同步
down_read(&nh->rwsem);
// 进行基本回调处理
ret = notifier_call_chain(&nh->head, val, v);
up_read(&nh->rwsem);
return ret;
}
EXPORT_SYMBOL_GPL(blocking_notifier_call_chain);
4.3 原始通知块操作
和基本原始块操作完全相同:
int raw_notifier_chain_register(struct raw_notifier_head *nh,
struct notifier_block *n)
{
return notifier_chain_register(&nh->head, n);
}
EXPORT_SYMBOL_GPL(raw_notifier_chain_register);
int raw_notifier_chain_unregister(struct raw_notifier_head *nh,
struct notifier_block *n)
{
return notifier_chain_unregister(&nh->head, n);
}
EXPORT_SYMBOL_GPL(raw_notifier_chain_unregister);
int raw_notifier_call_chain(struct raw_notifier_head *nh,
unsigned long val, void *v)
{
return notifier_call_chain(&nh->head, val, v);
}
EXPORT_SYMBOL_GPL(raw_notifier_call_chain);
4.4 SRCU通知块操作
4.4.1 登记
必须在进程的上下文中调用, 和blocking通知类似
int srcu_notifier_chain_register(struct srcu_notifier_head *nh,
struct notifier_block *n)
{
int ret;
/*
* This code gets used during boot-up, when task switching is
* not yet working and interrupts must remain disabled. At
* such times we must not call mutex_lock().
*/
if (unlikely(system_state == SYSTEM_BOOTING))
return notifier_chain_register(&nh->head, n);
mutex_lock(&nh->mutex);
ret = notifier_chain_register(&nh->head, n);
mutex_unlock(&nh->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(srcu_notifier_chain_register);
4.4.2 撤销
必须在进程的上下文中调用, 和blocking通知类似
int srcu_notifier_chain_unregister(struct srcu_notifier_head *nh,
struct notifier_block *n)
{
int ret;
/*
* This code gets used during boot-up, when task switching is
* not yet working and interrupts must remain disabled. At
* such times we must not call mutex_lock().
*/
if (unlikely(system_state == SYSTEM_BOOTING))
return notifier_chain_unregister(&nh->head, n);
mutex_lock(&nh->mutex);
ret = notifier_chain_unregister(&nh->head, n);
mutex_unlock(&nh->mutex);
synchronize_srcu(&nh->srcu);
return ret;
}
EXPORT_SYMBOL_GPL(srcu_notifier_chain_unregister);
4.4.3 回调
在进程的上下文中调用, 可以阻塞:
int srcu_notifier_call_chain(struct srcu_notifier_head *nh,
unsigned long val, void *v)
{
int ret;
int idx;
// 使用srcu读锁来加锁
idx = srcu_read_lock(&nh->srcu);
ret = notifier_call_chain(&nh->head, val, v);
srcu_read_unlock(&nh->srcu, idx);
return ret;
}
EXPORT_SYMBOL_GPL(srcu_notifier_call_chain);
4.4.4 初始化
因为SRCU通知不能通过宏来初始化, 必须要专门定义一个初始化函数来初始化srcu的通知块参数:
void srcu_init_notifier_head(struct srcu_notifier_head *nh)
{
// 初始化锁
mutex_init(&nh->mutex);
// 初始化scru结构
if (init_srcu_struct(&nh->srcu) < 0)
BUG();
nh->head = NULL;
}
EXPORT_SYMBOL_GPL(srcu_init_notifier_head);
5. 应用
下面以连接跟踪中的事件处理来说明, 就是通过通知块来实现的:
初始化定义一个静态的原子通知头参数:
/* net/ipv4/netfilter/ip_conntrack_core.c */
ATOMIC_NOTIFIER_HEAD(ip_conntrack_chain);
连接跟踪的事件处理函数, 实际就是通知回调函数:
/* include/linux/netfilter_ipv4/ip_conntrack.h */
// 连接事件处理
static inline void ip_conntrack_event(enum ip_conntrack_events event,
struct ip_conntrack *ct)
{
// 判断连接是否合法
if (is_confirmed(ct) && !is_dying(ct))
// 调用原子通知回调函数, 执行登记的回调函数
atomic_notifier_call_chain(&ip_conntrack_chain, event, ct);
}
连接跟踪相关事件的登记和撤销:
/* include/linux/netfilter_ipv4/ip_conntrack.h */
// 就是标准的原子通知登记和撤销函数
static inline int ip_conntrack_register_notifier(struct notifier_block *nb)
{
return atomic_notifier_chain_register(&ip_conntrack_chain, nb);
}
static inline int ip_conntrack_unregister_notifier(struct notifier_block *nb)
{
return atomic_notifier_chain_unregister(&ip_conntrack_chain, nb);
}
在net/ipv4/netfilter/ip_conntrack_netlink.c中定义了netlink通知回调函数:
static struct notifier_block ctnl_notifier = {
.notifier_call = ctnetlink_conntrack_event,
};
......
ret = ip_conntrack_register_notifier(&ctnl_notifier);
......
这样, 在任何地方ip_conntrack_event()函数时就会调用到该netlink通知回调函数.
6. 结论
notify通知块处理是内核中的一种回调处理机制, 一般不是直接调用原始的通知处理函数, 而是根据
要完成的功能, 如事件回调, 重启回调等重新定义新的处理函数, 然后在必要的地方调用相应的回调
包装函数就可以实现回调。
内核通知链
1.1. 概述
Linux内核中各个子系统相互依赖,当其中某个子系统状态发生改变时,就必须使用一定的机制告知使用其服务的其他子系统,以便其他子系统采取相应的措施。为满足这样的需求,内核实现了事件通知链机制(notificationchain)。
通知链只能用在各个子系统之间,而不能在内核和用户空间进行事件的通知。组成内核的核心系统代码均位于kernel目录下,通知链表位于kernel/notifier.c中,对应的头文件为include/linux/notifier.h。通知链表机制并不复杂,实现它的代码只有区区几百行。
事件通知链表是一个事件处理函数的列表,每个通知链都与某个或某些事件有关,当特定的事件发生时,就调用相应的事件通知链中的回调函数,进行相应的处理。
1.2.数据结构
如图 1中所示,Linux的网络子系统一共有3个通知链:表示ipv4地址发生变化时的inetaddr_chain;表示ipv6地址发生变化的inet6addr_chain;还有表示设备注册、状态变化的netdev_chain。
在这些链中都是一个个notifier_block结构:
- struct notifier_block {
- int (*notifier_call)(struct notifier_block *, unsigned long, void *);
- struct notifier_block *next;
- int priority;
- };
struct notifier_block {
int (*notifier_call)(struct notifier_block *, unsigned long, void *);
struct notifier_block *next;
int priority;
};
其中,
1. notifier_call:当相应事件发生时应该调用的函数,由被通知方提供,如other_subsys_1;
2. notifier_block *next:用于链接成链表的指针;
3. priority:回调函数的优先级,一般默认为0。
内核代码中一般把通知链命名为xxx_chain, xxx_nofitier_chain这种形式的变量名。围绕核心数据结构notifier_block,内核定义了四种通知链类型:
1. 原子通知链( Atomic notifier chains ):通知链元素的回调函数(当事件发生时要执行的函数)在中断或原子操作上下文中运行,不允许阻塞。对应的链表头结构:
- struct atomic_notifier_head {
- spinlock_t lock;
- struct notifier_block *head;
- };
struct atomic_notifier_head {
spinlock_t lock;
struct notifier_block *head;
};
2. 可阻塞通知链( Blocking notifier chains ):通知链元素的回调函数在进程上下文中运行,允许阻塞。对应的链表头:
- struct blocking_notifier_head {
- struct rw_semaphore rwsem;
- struct notifier_block *head;
- };
struct blocking_notifier_head {
struct rw_semaphore rwsem;
struct notifier_block *head;
};
3. 原始通知链( Raw notifierchains ):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。对应的链表头:
网络子系统就是该类型,通过以下宏实现head的初始化
- static RAW_NOTIFIER_HEAD(netdev_chain);
- #define RAW_NOTIFIER_INIT(name) { \
- .head= NULL }
- #define RAW_NOTIFIER_HEAD(name) \ //调用他就好了
- struct raw_notifier_head name = \
- RAW_NOTIFIER_INIT(name)
- 即:
- struct raw_notifier_head netdev_chain = {
- .head = NULL;
- }
static RAW_NOTIFIER_HEAD(netdev_chain);
#define RAW_NOTIFIER_INIT(name) { \
.head= NULL }
#define RAW_NOTIFIER_HEAD(name) \ //调用他就好了
struct raw_notifier_head name = \
RAW_NOTIFIER_INIT(name)
即:
struct raw_notifier_head netdev_chain = {
.head = NULL;
}
而其回调函数的注册,比如向netdev_chain的注册函数:register_netdevice_notifier。
- struct raw_notifier_head {
- struct notifier_block *head;
- };
struct raw_notifier_head {
struct notifier_block *head;
};
4. SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体。对应的链表头:
- struct srcu_notifier_head {
- struct mutex mutex;
- struct srcu_struct srcu;
- struct notifier_block *head;
- };
struct srcu_notifier_head {
struct mutex mutex;
struct srcu_struct srcu;
struct notifier_block *head;
};
1.3. 运行机理
被通知一方(other_subsys_x)通过notifier_chain_register向特定的chain注册回调函数,并且一般而言特定的子系统会用特定的notifier_chain_register包装函数来注册,比如路由子系统使用的是网络子系统的:register_netdevice_notifier来注册他的notifier_block。
1.3.1. 向事件通知链注册的步骤
1. 申明struct notifier_block结构
2. 编写notifier_call函数
3. 调用特定的事件通知链的注册函数,将notifier_block注册到通知链中
如果内核组件需要处理够某个事件通知链上发出的事件通知,其就该在初始化时在该通知链上注册回调函数。
1.3.2. 通知子系统有事件发生
inet_subsys是通过notifier_call_chain来通知其他的子系统(other_subsys_x)的。
notifier_call_chain会按照通知链上各成员的优先级顺序执行回调函数(notifier_call_x);回调函数的执行现场在notifier_call_chain进程地址空间;其返回值是NOTIFY_XXX的形式,在include/linux/notifier.h中:
- #define NOTIFY_DONE 0x0000 /* 对事件视而不见 */
- #define NOTIFY_OK 0x0001 /* 事件正确处理 */
- #define NOTIFY_STOP_MASK 0x8000 /*由notifier_call_chain检查,看继续调用回调函数,还是停止,_BAD和_STOP中包含该标志 */
- #define NOTIFY_BAD (NOTIFY_STOP_MASK|0x0002) /*事件处理出错,不再继续调用回调函数 */
- /*
- *Clean way to return from the notifier and stop further calls.
- */
- #define NOTIFY_STOP (NOTIFY_OK|NOTIFY_STOP_MASK) /* 回调出错,不再继续调用该事件回调函数 */
#define NOTIFY_DONE 0x0000 /* 对事件视而不见 */
#define NOTIFY_OK 0x0001 /* 事件正确处理 */
#define NOTIFY_STOP_MASK 0x8000 /*由notifier_call_chain检查,看继续调用回调函数,还是停止,_BAD和_STOP中包含该标志 */
#define NOTIFY_BAD (NOTIFY_STOP_MASK|0x0002) /*事件处理出错,不再继续调用回调函数 */
/*
*Clean way to return from the notifier and stop further calls.
*/
#define NOTIFY_STOP (NOTIFY_OK|NOTIFY_STOP_MASK) /* 回调出错,不再继续调用该事件回调函数 */
notifier_call_chain捕获并返回最后一个事件处理函数的返回值;注意:notifier_call_chain可能同时被不同的cpu调用,故而调用者必须保证互斥。
1.3.3. 事件列表
对于网络子系统而言,其事件常以NETDEV_XXX命名;描述了网络设备状态(dev->flags)、传送队列状态(dev->state)、设备注册状态(dev->reg_state),以及设备的硬件功能特性(dev->features):
include/linux/notifier.h中
- /* netdevice notifier chain */
- #define NETDEV_UP 0x0001 /* 激活一个网络设备 */
- #define NETDEV_DOWN 0x0002f /* 停止一个网络设备,所有对该设备的引用都应释放 */
- #define NETDEV_REBOOT 0x0003 /* 检查到网络设备接口硬件崩溃,硬件重启 */
- #define NETDEV_CHANGE 0x0004 /* 网络设备的数据包队列状态发生改变 */
- #define NETDEV_REGISTER 0x0005 /*一个网络设备事例注册到系统中,但尚未激活 */
- #define NETDEV_UNREGISTER 0x0006 /*网络设备驱动已卸载 */
- #define NETDEV_CHANGEMTU 0x0007 /*MTU发生了改变 */
- #define NETDEV_CHANGEADDR 0x0008 /*硬件地址发生了改变 */
- #define NETDEV_GOING_DOWN 0x0009 /*网络设备即将注销,有dev->close报告,通知相关子系统处理 */
- #define NETDEV_CHANGENAME 0x000A /*网络设备名改变 */
- #define NETDEV_FEAT_CHANGE 0x000B /*feature网络硬件功能改变 */
- #define NETDEV_BONDING_FAILOVER 0x000C /* */
- #define NETDEV_PRE_UP 0x000D /* */
- #define NETDEV_BONDING_OLDTYPE 0x000E /* */
- #define NETDEV_BONDING_NEWTYPE 0x000F /* */
/* netdevice notifier chain */
#define NETDEV_UP 0x0001 /* 激活一个网络设备 */
#define NETDEV_DOWN 0x0002f /* 停止一个网络设备,所有对该设备的引用都应释放 */
#define NETDEV_REBOOT 0x0003 /* 检查到网络设备接口硬件崩溃,硬件重启 */
#define NETDEV_CHANGE 0x0004 /* 网络设备的数据包队列状态发生改变 */
#define NETDEV_REGISTER 0x0005 /*一个网络设备事例注册到系统中,但尚未激活 */
#define NETDEV_UNREGISTER 0x0006 /*网络设备驱动已卸载 */
#define NETDEV_CHANGEMTU 0x0007 /*MTU发生了改变 */
#define NETDEV_CHANGEADDR 0x0008 /*硬件地址发生了改变 */
#define NETDEV_GOING_DOWN 0x0009 /*网络设备即将注销,有dev->close报告,通知相关子系统处理 */
#define NETDEV_CHANGENAME 0x000A /*网络设备名改变 */
#define NETDEV_FEAT_CHANGE 0x000B /*feature网络硬件功能改变 */
#define NETDEV_BONDING_FAILOVER 0x000C /* */
#define NETDEV_PRE_UP 0x000D /* */
#define NETDEV_BONDING_OLDTYPE 0x000E /* */
#define NETDEV_BONDING_NEWTYPE 0x000F /* */
1.4. 简单一例:
通过上面所述,notifier_chain机制只能在内核个子系统间使用,因此,这里使用3个模块:test_notifier_chain_0、test_notifier_chain_1、test_notifier_chain_2;当 test_notifier_chain_2通过module_init初始化模块时发出事件TESTCHAIN_2_INIT;然后 test_notifier_chain_1作出相应的处理:打印 test_notifier_chain_2正在初始化。
- /* test_chain_0.c :0. 申明一个通知链;1. 向内核注册通知链;2. 定义事件; 3. 导出符号,因而必需最后退出*/
- #include <linux/notifier.h>
- #include <linux/module.h>
- #include <linux/init.h>
- #include <linux/kernel.h> /* printk() */
- #include <linux/fs.h> /* everything() */
- #define TESTCHAIN_INIT 0x52U
- static RAW_NOTIFIER_HEAD(test_chain);
- /* define our own notifier_call_chain */
- static int call_test_notifiers(unsigned long val, void *v)
- {
- return raw_notifier_call_chain(&test_chain, val, v);
- }
- EXPORT_SYMBOL(call_test_notifiers);
- /* define our own notifier_chain_register func */
- static int register_test_notifier(struct notifier_block *nb)
- {
- int err;
- err = raw_notifier_chain_register(&test_chain, nb);
- if(err)
- goto out;
- out:
- return err;
- }
- EXPORT_SYMBOL(register_test_notifier);
- static int __init test_chain_0_init(void)
- {
- printk(KERN_DEBUG "I'm in test_chain_0\n");
- return 0;
- }
- static void __exit test_chain_0_exit(void)
- {
- printk(KERN_DEBUG "Goodbye to test_chain_0\n");
- // call_test_notifiers(TESTCHAIN_EXIT, (int *)NULL);
- }
- MODULE_LICENSE("GPL v2");
- MODULE_AUTHOR("fishOnFly");
- module_init(test_chain_0_init);
- module_exit(test_chain_0_exit);
- /* test_chain_1.c :1. 定义回调函数;2. 定义notifier_block;3. 向chain_0注册notifier_block;*/
- #include <linux/notifier.h>
- #include <linux/module.h>
- #include <linux/init.h>
- #include <linux/kernel.h> /* printk() */
- #include <linux/fs.h> /* everything() */
- extern int register_test_notifier(struct notifier_block *nb);
- #define TESTCHAIN_INIT 0x52U
- /* realize the notifier_call func */
- int test_init_event(struct notifier_block *nb, unsigned long event,
- void *v)
- {
- switch(event){
- case TESTCHAIN_INIT:
- printk(KERN_DEBUG "I got the chain event: test_chain_2 is on the way of init\n");
- break;
- default:
- break;
- }
- return NOTIFY_DONE;
- }
- /* define a notifier_block */
- static struct notifier_block test_init_notifier = {
- .notifier_call = test_init_event,
- };
- static int __init test_chain_1_init(void)
- {
- printk(KERN_DEBUG "I'm in test_chain_1\n");
- register_test_notifier(&test_init_notifier);<SPAN style="WHITE-SPACE: pre"> </SPAN>// 由chain_0提供的设施
- return 0;
- }
- static void __exit test_chain_1_exit(void)
- {
- printk(KERN_DEBUG "Goodbye to test_clain_l\n");
- }
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("fishOnFly");
- module_init(test_chain_1_init);
- module_exit(test_chain_1_exit);
- /* test_chain_2.c:发出通知链事件*/
- #include <linux/notifier.h>
- #include <linux/module.h>
- #include <linux/init.h>
- #include <linux/kernel.h> /* printk() */
- #include <linux/fs.h> /* everything() */
- extern int call_test_notifiers(unsigned long val, void *v);
- #define TESTCHAIN_INIT 0x52U
- static int __init test_chain_2_init(void)
- {
- printk(KERN_DEBUG "I'm in test_chain_2\n");
- call_test_notifiers(TESTCHAIN_INIT, "no_use");
- return 0;
- }
- static void __exit test_chain_2_exit(void)
- {
- printk(KERN_DEBUG "Goodbye to test_chain_2\n");
- }
- MODULE_LICENSE("GPL v2");
- MODULE_AUTHOR("fishOnFly");
- module_init(test_chain_2_init);
- module_exit(test_chain_2_exit);
- # Makefile
- # Comment/uncomment the following line to disable/enable debugging
- # DEBUG = y
- # Add your debugging flag (or not) to CFLAGS
- ifeq ($(DEBUG),y)
- DEBFLAGS = -O -g -DSCULL_DEBUG # "-O" is needed to expand inlines
- else
- DEBFLAGS = -O2
- endif
- ifneq ($(KERNELRELEASE),)
- # call from kernel build system
- obj-m := test_chain_0.o test_chain_1.o test_chain_2.o
- else
- KERNELDIR ?= /lib/modules/$(shell uname -r)/build
- PWD := $(shell pwd)
- modules:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
- endif
- clean:
- rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
- depend .depend dep:
- $(CC) $(CFLAGS) -M *.c > .depend
- ifeq (.depend,$(wildcard .depend))
- include .depend
- endif
- [wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_0.ko
- [wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_1.ko
- [wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_2.ko
- [wang2@iwooing: notifier_chian]$ dmesg
- [ 5950.112649] I'm in test_chain_0
- [ 5956.766610] I'm in test_chain_1
- [ 5962.570003] I'm in test_chain_2
- [ 5962.570008] I got the chain event: test_chain_2 is on the way of init
- [ 6464.042975] Goodbye to test_chain_2
- [ 6466.368030] Goodbye to test_clain_l
- [ 6468.371479] Goodbye to test_chain_0
/* test_chain_0.c :0. 申明一个通知链;1. 向内核注册通知链;2. 定义事件; 3. 导出符号,因而必需最后退出*/
#include <linux/notifier.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/fs.h> /* everything() */
#define TESTCHAIN_INIT 0x52U
static RAW_NOTIFIER_HEAD(test_chain);
/* define our own notifier_call_chain */
static int call_test_notifiers(unsigned long val, void *v)
{
return raw_notifier_call_chain(&test_chain, val, v);
}
EXPORT_SYMBOL(call_test_notifiers);
/* define our own notifier_chain_register func */
static int register_test_notifier(struct notifier_block *nb)
{
int err;
err = raw_notifier_chain_register(&test_chain, nb);
if(err)
goto out;
out:
return err;
}
EXPORT_SYMBOL(register_test_notifier);
static int __init test_chain_0_init(void)
{
printk(KERN_DEBUG "I'm in test_chain_0\n");
return 0;
}
static void __exit test_chain_0_exit(void)
{
printk(KERN_DEBUG "Goodbye to test_chain_0\n");
// call_test_notifiers(TESTCHAIN_EXIT, (int *)NULL);
}
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("fishOnFly");
module_init(test_chain_0_init);
module_exit(test_chain_0_exit);
/* test_chain_1.c :1. 定义回调函数;2. 定义notifier_block;3. 向chain_0注册notifier_block;*/
#include <linux/notifier.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/fs.h> /* everything() */
extern int register_test_notifier(struct notifier_block *nb);
#define TESTCHAIN_INIT 0x52U
/* realize the notifier_call func */
int test_init_event(struct notifier_block *nb, unsigned long event,
void *v)
{
switch(event){
case TESTCHAIN_INIT:
printk(KERN_DEBUG "I got the chain event: test_chain_2 is on the way of init\n");
break;
default:
break;
}
return NOTIFY_DONE;
}
/* define a notifier_block */
static struct notifier_block test_init_notifier = {
.notifier_call = test_init_event,
};
static int __init test_chain_1_init(void)
{
printk(KERN_DEBUG "I'm in test_chain_1\n");
register_test_notifier(&test_init_notifier);<span style="WHITE-SPACE: pre"> </span>// 由chain_0提供的设施
return 0;
}
static void __exit test_chain_1_exit(void)
{
printk(KERN_DEBUG "Goodbye to test_clain_l\n");
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("fishOnFly");
module_init(test_chain_1_init);
module_exit(test_chain_1_exit);
/* test_chain_2.c:发出通知链事件*/
#include <linux/notifier.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/fs.h> /* everything() */
extern int call_test_notifiers(unsigned long val, void *v);
#define TESTCHAIN_INIT 0x52U
static int __init test_chain_2_init(void)
{
printk(KERN_DEBUG "I'm in test_chain_2\n");
call_test_notifiers(TESTCHAIN_INIT, "no_use");
return 0;
}
static void __exit test_chain_2_exit(void)
{
printk(KERN_DEBUG "Goodbye to test_chain_2\n");
}
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("fishOnFly");
module_init(test_chain_2_init);
module_exit(test_chain_2_exit);
# Makefile
# Comment/uncomment the following line to disable/enable debugging
# DEBUG = y
# Add your debugging flag (or not) to CFLAGS
ifeq ($(DEBUG),y)
DEBFLAGS = -O -g -DSCULL_DEBUG # "-O" is needed to expand inlines
else
DEBFLAGS = -O2
endif
ifneq ($(KERNELRELEASE),)
# call from kernel build system
obj-m := test_chain_0.o test_chain_1.o test_chain_2.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
depend .depend dep:
$(CC) $(CFLAGS) -M *.c > .depend
ifeq (.depend,$(wildcard .depend))
include .depend
endif
[wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_0.ko
[wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_1.ko
[wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_2.ko
[wang2@iwooing: notifier_chian]$ dmesg
[ 5950.112649] I'm in test_chain_0
[ 5956.766610] I'm in test_chain_1
[ 5962.570003] I'm in test_chain_2
[ 5962.570008] I got the chain event: test_chain_2 is on the way of init
[ 6464.042975] Goodbye to test_chain_2
[ 6466.368030] Goodbye to test_clain_l
[ 6468.371479] Goodbye to test_chain_0