内核的很多子系统之间具有很强的相互依赖性,因此,其中一个子系统侦测到的或者产生的事件,其他子系统可能都有兴趣。为了实现这种交互需求,Linux使用了所谓的通知链(notification chain)
这篇博客你将看到:
假设我们有如下所示带有4个接口的Linux路由器。该图显示了路由器与五个网络之间的关系,还有一份简化的路由表。
网络A直接连接至RT的接口eth0,而网络F没有直接连接至RT,但是RT的eth3则直接连接至另一个路由器,其接口为IP1,并且第二个路预期知道该如何联系网络F。其他的案例都很类似。简而言之,有些网路是直接连接的,而其他网络则需要一个或多个附加的路由器协助才能联系到。
有关路由代码如何处理这种情况的详细描述,后面分析。今天主要的介绍的是通知链
角色。假设接口eth3由于网络断线使得管理命令(如:ifconfgi eth3 down)失效或者造成硬件失败,结果RT无法联系网络D、E和F(以及A、B、C网络中依赖RT联机的系统),因此应该从路由表中删除。谁将通知路由子系统该接口的失效?那就是通知链。
分析下面这个稍微复杂的案例,路由子系统会与动态路由协议交互,这种协议可以调整一个或多个路由表,使其吻合网络拓扑结构,因此当拓扑结构许可时就可以处理接口失败的问题(也就是有冗余路径存在)。
上图中,RT通过网络A和网路E而联系网络F。最初会选择R是因为其成本较少,但现在E无法联系,因此,路由表应该更新网络F的路由改走网络A。这种决策的基础可能包括一些本地主机事件,诸如设备注册和删除,以及路由配置中的复杂因素和所用的路由协议。在任何情况下,管理路由表的路由子系统必须从其他子系统哪里收到相关的信息通知,因此产生了通知链的需求。
通知链就是一份简单的函数列表,当给定事件发生时予以执行,每个函数都让另一个子系统知道,调用此函数的子系统内所发生的的一个事件或者子系统所侦测到的一个事件。
因此,就每条通知链而言,都有被动端(被通知者)和主动端(通知者),也就是所谓的发布-订阅(publish-and-subscribe)模型
:
通知链的使用使源码更易于编写和维护。想象一下,一个通用函数不使用通知链,该如何把一个事件通知给外部子系统:
if(subsystem_X_enabled)
{
do_something_1;
}
if(subsystem_Y_enabled)
{
do_something_2;
}
if(subsystem_Z_enabled)
{
do_something_3;
}
换言之,必须为每个可能对一个事件感兴趣的子系统都引入一个条件子句,结果每次某人添加一个子系统给内核时,此子系统的维护者就得添加一个子句。
子系统维护者不可能去追踪每个被添加到内核的子系统。然而,每位子系统维护者应该知道:
通知链列表元素的类型是notifier_block
,其定义如下:
struct notifier_block
{
int (*notifier_call)(struct notifirt_block *self, unsigned long, void *);
struct notifier_block *next;
int priority;
};
notifier_call
是要执行的函数,next是用于链接列表的元素,而priority代表的是该函数的优先级。较高优先级的函数会被先执行。但是实际中,注册时都不会理会notifier_block中定义的priority,意味着获得其默认值0.因此,执行的次序仅依赖于注册次序(也就是半随机次序)。notifier_call的返回值分析见下面的链上的通知事件一节
。
notifier_block实例的常见名称有xxx_chain、xxx_notifier_chain以及xxx_notifier_list。
当一个内核组件对给定通知链的事件感兴趣时,可以用通用函数notifier_chain_register
予以注册。内核也提供一组内涵notifier_chain_register
的包裹函数,如下列出了主要的API以及相关的包裹函数,可用于向三条链inetaddr_chain、inet6addr_chain以及netdev_chain注册及删除。
对于每条链条而言,那些notifier_block
实体被插入到一个按优先级排序的列表中。相同优先级的元素则按插入时间排序:新的元素排在尾端。
对通知链的访问受到notifier_lock
锁的保护。所有通知链只用一个锁并不是什么大不了的限制,而且也不影响性能,因此子系统通常只是在引导或模块加载时注册其notifier_call
函数,然后从那时起对列表的访问都是只读的(即共享)。
因为调用notifer_chain_register函数就是把回调函数插入到所有列表中,因此必须把列表指定为输入参数。然而,此函数很少被直接调用,通常是改用通用的包裹函数。
int notifier_chain_register(struct notifer_block **list, struct notifier_block *n)
{
write_lock(¬ifier_lock);
while(*list)
{
if(n->priority > (*list)->priority)
break;
list = &((*list)->next);
}
n->next = *list;
*list = n;
write_unlock(¬ifier_lock);
return 0;
}
通知信息由定义于kernelsys.c中的notifier_call_chain
产生,而此函数只是按优先级次序调用对此链注册的所有回调函数。注意,回调函数是在调用notifier_call_chian
的进程上下文(context)中执行的。然而,回调函数也是可以实现成把通知信息排进某处的队列,然后唤醒查看此通知信息的进程。
int notifier_call_chain(struct notifier_block **n, unsigned long val, void *v)
{
int ret = NOTIFY_DONE;
struct notifier_block *nb = *n;
while(*nb)
{
ret = nb->notifier_call(nb, vall, v);
if(ret & NOTIFY_STOP_MASK)
{
return ret
}
nb = nb->next;
}
return ret;
}
以下是其三个输入参数的意义:
net_device
数据结构。notifier_call_chain所调用的回调函数可以返回定义在include/linux/notifier.h
中的任何NOTIFY_XXX的值:
内核定义了至少10种不同的通知链。我们感兴趣的是那些对网络代码表征特别重要事件的通知链。主要是:
inetaddr_chain
发送有关本地接口上的IPv4地址的插入、删除以及变更的通知信息。在后面的博客会描述这类通知信息何时产生。IPv6使用类似的链(ipnet6addr_chain)
inetdev_chain
发送有关网络设备注册状态的通知信息。在后面会描述这类通知信息何时产生。
对于网络子系统所用的这些链以及其他链而言,其目的和使用都会在相关的关于通知者子系统中描述。
网络代码也可以注册其他内核组件产生的通知信息。例如,某些NIC设备驱动程序可以用reboot_notifier_list
链注册;当系统重新引导时,此链会发出警告。
大多数通知链都有一组包裹函数,可以用于注册和删除。例如,下列包裹函数可用于向netdev_chain注册:
int register_netdev_notifier(struct notifier_block *nb)
{
return notifier_chain_register(&netdev_chain,nb);
}
包裹函数常见名称包括[un]register_xxx_notifier、xxx_[un]register_notifier以及xxx_[un]register。
通知链注册通常发生在感兴趣的内核组件初始化时。例如,下面是从net/ipv4/fib_fronted.c
中取出的片段;ip_fib_init就是路由代码所使用的初始化函数。
static struct notifier_block fib_inetaddr_notifier = {
.notifier_call = fib_inetaddre_event,
};
static struct notifier_block fib_netdev_notifier = {
.notifier_call = fib_netdev_event,
};
void __init ip_fib_init(void)
{
........
register_netdevice_notifier(&fib_netdev_notifier);
register_netaddr_notifier(&fib_inetaddr_notifier);
}
改变本地IP地址配置以及改变本地设备的注册状态,都会影响路由表。
就这篇博客而言,与/proc中的文件无关。
名称 | 描述 |
---|---|
函数和宏 | |
notifier_chain_register +包裹函数 | 前两个函数是为了通知链注册和删除回调处理 |
notifier_chain_unregister + 包裹函数 | |
notifier_call_chain | 送出特定种类事件的所有通知信息 |
数据结构 | |
struct notifier_block | 定义通知信息的处理函数,包括要调用的回调函数 |