深入理解Linux网络技术内幕——Notification内核通知表链
为什么要有内核通知表链:
Linux由多个相互依赖的子系统组成。其中一些子系统可能需要对其他子系统的一些事件感兴趣。这样子系统之间需要一些通信机制来实现这一功能。
在接触Notification Chain之前,我们可能想到通过轮询来实现,事件发生时,子系统轮询所有其他的子系统,看看有没有对这一事件感兴趣的,有没有需要执行的子函数。
If (subsystem_X_enabled) { do_something_1 } if (subsystem_Y_enabled) { do_something_2 } If (subsystem_Z_enabled) { do_something_3 } ... ... ...
这种方式看起来可以满足我们的需要,但是代码太繁杂了,会导致每个子系统变得很庞大。而Notification
Chain则大大的改善了这些不足。
Notification Chain基本原理
Notification Chain是一种“发布——订阅”模型,当某个子系统发生某一事件时,它就通知所有对这一事件感兴趣的子系统,以便执行响应函数。在这个模型中,发生事件的子系统是主动端,对事件感兴趣的子系统是被动端。(本文也将被动端成为被通知端)。 在事件发生时,检测或产生事件的子系统作为主动一方通过通知函数来告知作为被动一方的订阅者(对此事件感兴趣的子系统)。这里有个额外要求,订阅一方要提供callback函数以供发布方调用,当然,提供什么样的callback函数完全由订阅方决定。
通知表链模型图:
Notification Chain源代码解析
结构体:
//通知块 struct notifier_block { int (*notifier_call)(struct notifier_block *self, unsigned long, void *);//回调函数 struct notifier_block *next; // int priority; //注册的优先级,用户自行指定,优先级越高回调函数就越早执行 };
通知表链的定义
Linux内核中有三种通知表链:
//原子通知表链 struct atomic_notifier_head { spinlock_t lock; struct notifier_block __rcu *head; }; //可阻塞通知表链 struct blocking_notifier_head { struct rw_semaphore rwsem; struct notifier_block __rcu *head; }; //原始通知表链 struct raw_notifier_head { struct notifier_block __rcu *head; }; //可阻塞通知表链的变体 struct srcu_notifier_head { struct mutex mutex; struct srcu_struct srcu; struct notifier_block __rcu *head; };
需要定义一个通知表链时,用XXX_NOTIFIER_HEAD(name)来定义,下面以RAW_NOTIFIER_HEAD为例:
static RAW_NOTIFIER_HEAD(netdev_chain);
追踪 RAW_NOTIFIER_HEAD,
#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)
继续
#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 } /* srcu_notifier_heads cannot be initialized statically */
由此完成了一条通知表链的定义和初始化。
Linux 内核中有许多个notification chain,我们常用到的有三个netdev_chain,inetaddr_chain,inet6addr_chain。结合struct notifier_block,每条chain上有一个回调函数的队列(链表)notifier_call ,那么这些回调函数(notification通知块)如何使用,合适调用呢。
首先我们在穿件通知表链之初,通知表链只有链表头,没有其他的notifier_block块,这个链表会关注一些事件(如netdev_chain会关注设备加入删除等,具体下文会做更清楚的介绍。)如果某个子系统对这条chain关注的时间感兴趣,它就注册一个notifier_block到链表中。notifier_block块的注册和注销,以下面形式实现:
int notifier_chain_register(struct notifier_block **list, struct notifier_block *n) int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n)
这两个是封装函数,具体的通知链都会对这两个函数进行封装,定义自己的注册和注销函数。以netdev_chain为例,封装过程:
int register_netdevice_notifier(struct notifier_block *nb) { raw_notifier_chain_register(&netdev_chain, nb); } int raw_notifier_chain_register(struct raw_notifier_head *nh, struct notifier_block *n) { return notifier_chain_register(&nh->head, n); }
某个子系统需要关注设备的变化信息时,就将处理函数notifier_call(自己编写的)作为notifier_block注册的通知表链。
事件发生时,通知表链执行过程
当事件发生时,时间会调用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_raw(*nl); while (nb && nr_to_call) { next_nb = rcu_dereference_raw(nb->next); #ifdef CONFIG_DEBUG_NOTIFIERS if (unlikely(!func_ptr_is_kernel_text(nb->notifier_call))) { WARN(1, "Invalid notifier called!"); nb = next_nb; continue; } #endif 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; }