深入理解linux网络技术内幕
1 系统初始化
1.1 通知链
为什么需要通知链?书中给出了一个例子,如果网络中一个子网不可达,必须更新路由器的路由表,而更新这个路由表就需要通知链。然后给出结论:In any case, the routing subsystem that manages the tables must be informed of the relevant information by some other subsystem, demonstrating the need for notification chains。在任何情况下,管理表的路由子系统都必须由其他子系统通知相关信息,这表明需要通知链。
通知链简单说就是给定事件发生时要执行的函数列表。通知链只用于内核子系统之间。内核和用户空间之间的通知采用其他机制。
其定义如下,也就是一串notifier_block组成的链表。
struct notifier_block { int (*notifier_call)(struct notifier_block *self, unsigned long, void *); //要执行的函数 struct notifier_block *next; //链中的下一个节点 int priority; //优先级 };
notifier_block实例的常见名称为xxx_chain、xxx_notifier_chain和xxx_notiifier_list
1.1.1 订阅通知
当一个kernel模块对一个指定的通知链感兴趣,那么它可以使用通用的notifier_chain_register函数来进行订阅(注册)它。
int notifier_chain_register(struct notifier_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; }
除了这个通用的注册函数,还有一些包装函数可供我们使用,如下列出了订阅和取消订阅相关的包装函数
Registration:
int notifier_chain_register(struct notifier_block **list, struct notifier_block *n)
Wrappers:
inetaddr_chain register_inetaddr_notifier //其中register_inetaddr_notifier是注册函数,执行该注册函数后将向inetaddr_chain这个通知链添加一个元素。
inet6addr_chain register_inet6addr_notifier
netdev_chain register_netdevice_notifier
Unregistration:
int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n)
Wrappers:
inetaddr_chain unregister_inetaddr_notifier
inet6addr_chain unregister_inet6addr_notifier
netdev_chain unregister_netdevice_notifier
Notification
int notifier_call_chain(struct notifier_block **n, unsigned long val, void *v)
1.1.2 通知链上的事件
通知是通过函数notifier_call_chain生成的。此函数会按优先级顺序调用所有链上注册的notifier_block节点中的回调例程(notifier_call)。
比如某个内核子模块某个task中调用了notifier_call_chain函数,导致通知链上的回调例程被调用,继而唤醒其他正在监听该回调例程的Task。
int notifier_call_chain(struct notifier_block **n, unsigned long val, void *v) // val为事件类型 { int ret = NOTIFY_DONE; struct notifier_block *nb = *n; while (nb) { ret = nb->notifier_call(nb, val, v); if (ret & NOTIFY_STOP_MASK) { return ret; } nb = nb->next; } return ret; }
回调函数会返回NOTIFY_OK,NOTIFY_DONE,NOTIFY_BAD,NOTIFY_STOP等值。注意看函数中,当出现NOTIFY_BAD,NOTIFY_STOP时将跳出循环并return ret;也就是说当通知链上某个事件回调函数执行异常时,将中止执行链上其他事件回调函数。
还有就是对于同一通知链,不同CPU同一时间执行notifier_call_chain函数,这时回调函数就要做好并发控制了。
1.1.3 网络子系统所用的通知链
内核定义了至少10种通知链,我们这里只关心网络中的通知链
inetaddr_chain:在本地接口上发送有关插入、删除和更改IP地址的通知。
netdev_chain:发送有关网络设备注册状态的通知