linux 通知链(notifier chain)
了解通知链(notifier chain)。1.通知链(notifier chain) notifier chain在Linux kernel中被用来实现不同的subsystem之间的通信。简单来说,notifier chain是一个存储了某个事件对应的回调函数的列表。每个notifier chain都和特定的事件相关,当发出事件通知的时候,会触发对应的回调函数来处理该事件。 四种不...
- 了解通知链(notifier chain)。
1.通知链(notifier chain)
Linux内核中各个子系统相互依赖,当其中某个子系统状态发生改变时,就必须使用一定的机制告知使用其服务的其他子系统,以便其他子系统采取相应的措施。为满足这样的需求,内核实现了事件通知链机制(notification chain)。
通知链只能用在各个子系统之间,而不能在内核态和用户态之间进行事件通知。内核的核心代码均位于kernel目录下,通知链表位于kernel/notifier.c中,对应的头文件为include/linux/notifier.h。
事件通知链是一个事件处理函数的列表,每个通知链都与某个或某些事件有关。当特定的事件发生时,就调用相应的事件通知链中的回调函数,进行相应的处理。
四种不同的notifier chain类型:
- atomic notifier:回调函数在原子上下文中执行,不可阻塞
- blocking notifier:回调函数在进程上下文中执行,可以阻塞
- raw notifier:回调函数没有任何限制,但加锁、保护等操作需要由调用者自行完成
- SRCU notifier: 回调函数同样是在进程上下文中执行的,是blocking notifier的一种变体
2.数据结构
Notifier chain的数据结构有notifier list和notifier block两个部分构成。notifier list就是用来存储notifier block的链表。
notifier chain根据类型不同分为四种notifier list结构:
- atomic_notifier_head: 使用spin lock保护链表,发送通知时使用RCU进行同步
struct atomic_notifier_head {
spinlock_t lock;
struct notifier_block __rcu *head;
};
- blocking_notifier_head: 使用读写信号量保护链表,发送通知使用RCU进行同步
struct blocking_notifier_head {
struct rw_semaphore rwsem;
struct notifier_block __rcu *head;
};
- raw_notifier_head: 没有保护链表的措施,发送通知使用RCU进行同步
struct raw_notifier_head {
struct notifier_block __rcu *head;
};
- srcu_notifier_head: 使用互斥锁保护链表,消息发送使用SRCU进行同步(没有使用锁机制),相比起传统的blocking notifier来说,在发送通知的时候由于保护chain所造成的开销更小
struct srcu_notifier_head {
struct mutex mutex;
struct srcu_struct srcu;
struct notifier_block __rcu *head;
};
事件对应的回调函数存储在notifier_block的notifier_call中,priority表示事件到来时回调执行的优先级,优先级越高的越早执行:
struct notifier_block {
notifier_fn_t notifier_call;
struct notifier_block __rcu *next;
int priority;
};
- notifier_call: 代表当事件发生之后调用的回调函数。
- next: 用来链接同一个类型的notifier。
- priority: notifier chain的优先级。对应的数字越大优先级越高,就优先执行。
其中回调函数是一个notifier_fn_t类型,可以接收两个额外参数—action和data,可以用来指定操作类型、传递数据指针。返回值则可以表明执行有无错误。
typedef int (*notifier_fn_t)(struct notifier_block *nb,
unsigned long action, void *data);
3.notifier chain初始化
内核提供了一套宏用来初始化各个类型的通知链。
3.1.动态初始化各个类型的通知链
#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)
3.2.静态初始化
113 #define ATOMIC_NOTIFIER_HEAD(name) \
114 struct atomic_notifier_head name = \
115 ATOMIC_NOTIFIER_INIT(name)
116 #define BLOCKING_NOTIFIER_HEAD(name) \
117 struct blocking_notifier_head name = \
118 BLOCKING_NOTIFIER_INIT(name)
119 #define RAW_NOTIFIER_HEAD(name) \
120 struct raw_notifier_head name = \
121 RAW_NOTIFIER_INIT(name)
122
123 #ifdef CONFIG_TREE_SRCU
124 #define _SRCU_NOTIFIER_HEAD(name, mod) \
125 static DEFINE_PER_CPU(struct srcu_data, name##_head_srcu_data); \
126 mod struct srcu_notifier_head name = \
127 SRCU_NOTIFIER_INIT(name, name##_head_srcu_data)
128
129 #else
130 #define _SRCU_NOTIFIER_HEAD(name, mod) \
131 mod struct srcu_notifier_head name = \
132 SRCU_NOTIFIER_INIT(name, name)
133
134 #endif
4.注册/注销通知链
注册和移除block的时候,由于不同类型使用了不同的方法来保护链表,所以需要调用相应类型的注册函数:
include/linux/notifier.h:
144 extern int atomic_notifier_chain_register(struct atomic_notifier_head *nh,
145 struct notifier_block *nb);
146 extern int blocking_notifier_chain_register(struct blocking_notifier_head *nh,
147 struct notifier_block *nb);
148 extern int raw_notifier_chain_register(struct raw_notifier_head *nh,
149 struct notifier_block *nb);
150 extern int srcu_notifier_chain_register(struct srcu_notifier_head *nh,
151 struct notifier_block *nb);
Note:在对于链表的保护方式上有所不同,但是真正操作链表的函数都是相同的,都是notifier_chain_register和notifier_chain_unregister。
- notifier_chain_register根据notifier_block的优先级,将新注册的notifier_block插入到单向链表中
- 移除的时候,同理将notifier_block从链表中移除。
5.通知函数
当某种事件需要发生的时候,就需要调用内核提供的通知函数notifier call函数,来通知注册过相应时间的子系统。同样,不同类型的notifier chain也需要使用不同的发送通知的函数。
- atomic notifier的回调函数需要在原子上下文中执行,所以回调函数不能够阻塞。内核采用RCU来保护notifier call back。
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);
}
- blocking notifier的回调函数可以允许阻塞。使用读写锁保护call back。
int __blocking_notifier_call_chain(struct blocking_notifier_head *nh,
unsigned long val, void *v,
int nr_to_call, int *nr_calls)
{
int ret = NOTIFY_DONE;
/*
* We check the head outside the lock, but if this access is
* racy then it does not matter what the result of the test
* is, we re-check the list after having taken the lock anyway:
*/
if (rcu_access_pointer(nh->head)) {
down_read(&nh->rwsem);
ret = notifier_call_chain(&nh->head, val, v, nr_to_call,
nr_calls);
up_read(&nh->rwsem);
}
return ret;
}
int blocking_notifier_call_chain(struct blocking_notifier_head *nh,
unsigned long val, void *v)
{
return __blocking_notifier_call_chain(nh, val, v, -1, NULL);
}
- raw notifier没有提供任何的锁机制,所以相关的锁操作必须由用户自己完成。
int raw_notifier_chain_register(struct raw_notifier_head *nh,
struct notifier_block *n)
{
return notifier_chain_register(&nh->head, n);
}
- srcu nitifier使用SRCU的方法来保护call back,这样在执行call chain的时候所造成开销是非常小的。然而相对的在register或者unregister的时候就会造成很大的开销。所以通常来说适合经常发送消息,但是很少会把notifier blocks移除的场景。
int __srcu_notifier_call_chain(struct srcu_notifier_head *nh,
unsigned long val, void *v,
int nr_to_call, int *nr_calls)
{
int ret;
int idx;
idx = srcu_read_lock(&nh->srcu);
ret = notifier_call_chain(&nh->head, val, v, nr_to_call, nr_calls);
srcu_read_unlock(&nh->srcu, idx);
return ret;
}
int srcu_notifier_call_chain(struct srcu_notifier_head *nh,
unsigned long val, void *v)
{
return __srcu_notifier_call_chain(nh, val, v, -1, NULL);
}
最终调用的都是notifier_call_chain函数,需要注意一下几个参数:
- nr_to_call参数表示调用的回调函数的数量,如果是-1的话这个参数无效
- nr_calls可以记录下发出了多少次消息,也就是执行了多少个回调函数
该函数的功能十分简单,遍历链表,根据设定的nr_to_call数量来执行回调,并且进行nr_calls的累加。当回调函数返回了NOTIFY_STOP的时候停止执行回调。最后该函数的返回值等于最后一次执行的回调函数的返回值。如果没有注册任何notifier block,返回NOTIFY_DONE.
notifier chain为我们提供了一种简单方便的不同子系统之间的通信方法。根据使用场景不同,使用不同类型的notifier chain可以获得更高的效率。此外这种方法不同于IPC通信,回调函数和消息发出会在同一个内核进程中执行,同样适用于一些对实时性要求较高的场合。
refer to:
- http://liujunming.top/2019/08/06/Linux-kernel-notifier-chain/
更多推荐
所有评论(0)