linux内核的通知链机制

一、为什么需要通知链:

   linux内核的各个子系统之间往往互相关联,一个子系统产生或者侦测到的事件,其它的子系统往往也很感兴趣,因此linux内核采用了通知链机制实现内核的子系统之间的通信需求。值的注意的是,通知链机制仅用于内核内部的子系统之间的通信,内核与用户空间的通信依赖于其它机制,如系统调用、procfs、sysctl、ioctl等。

   以下图为例,路由器RT直接连接网络A、B、C、E,间接连接网络D、F(通过另一个地址为IP1的路由器),其路由表如图。如果eth3连接网络E的通路断掉了(管理员下达指令ifconfigeth3down,或者硬件错误等原因),那么此时路由器RT就需要更新其路由表,如删除D、E、F网络相关的路由项目,那么谁去告诉路由器RT的路由子系统,说eth3的链路已经断开了呢,那就是通知链机制,也就是说此时会有内核的其它子系统发出通知消息,告诉路由子系统eth3接口已经断开,路由子系统接收到这个通知消息后就会更新自己的路由表。

linux内核的通知链机制

 

    对于上面的情形,考虑一个没有通知链机制的情景,内核的某个子系统产生或者检测到了一个事件,该子系统知道,内核的其它子系统可能也对此很感兴趣,于是它就会:

If (subsystem_X_enabled) {
do_something_1
}
if (subsystem_Y_enabled) {
do_something_2
}
If (subsystem_Z_enabled) {
do_something_3
}
... ... ...

    它觉得有几个子系统对这个事件感兴趣,那么它就需要几条相应的条件语句,首先,让它知道哪些子系统对此感兴趣就已经很困难了,而即使这点可以办到,还有一个更严重的问题,假设某一天内核被添加了一个子系统进来,而这个子系统对其它子系统产生的事件都可能感兴趣,那么怎么办?必须修改其它所有子系统对其事件的处理,也就是说其它每个子系统对于每个事件的处理函数里面都必须加上一条if语句,崩溃不?

    而如果采用通知链机制,也就是说某个子系统提供对事件进行处理的回调函数,其它子系统发来某个事件时,只要调用相应的回调函数就可以顺利地处理该事件。那即使内核再加入其它的子系统,也只需要在增加的子系统里面添加它感兴趣的事件的回调函数就可以,而不需要修改其它的子系统了。

 

二、什么是通知链机制     

   简单地说,通知链其实就是一组函数,这些函数在发生了某个事件时被触发执行,其结果就是通知其它的子系统发生了某个事件。对每个通知来说,都有一个主动方(通知者)和一个被动方(被通知者),也就是所谓的publish-and-subscribe模型:

    1.被通知方是通知的接收子系统,该子系统会提供一个回调函数

    2.通知方是通知的发送子系统,它会调用被动方提供的回调函数,具体执行哪个回调函数由被通知方决定,而不是产生事件的子系统。

   通知链机制实现了各个子系统之间的事件信息共享功能,每个子系统只需要关心其它子系统可能产生的事件哪些是自己感兴趣的,或者自己产生的事件对其它哪些子系统有意义就可以,而不需要关心自己感兴趣的事件是怎样产生的,自己产生的事件被关注的子系统是具体的哪个,以及它为什么会对此感兴趣等等。

 

三、怎样定义一个链

通知链列表元素的类型是

struct notifier_block
{
int (*notifier_call)(struct notifier_block *self, unsigned long,void *);
struct notifier_block *next;
int priority;
};

notifier_call就是回调函数,priority是函数的优先级,但实际实现过程中,这个值通常不设置,即让它取默认值0,因此回调函数的执行顺序就决定于注册的顺序(半随机顺序)。定义某个notifier_block实例时通常使用的名字如xxx_chain,xxx_notifier_chain, xxx_notifier_list.

 

四、注册一个链(将某个链元素插入list)

定义好一个链,还需要对它进行注册。当某个子系统对定义的某个通知链感兴趣时,它就会使用notifier_chain_register函数来注册这个链。这个函数还有一些包裹函数,如对于inetaddr_chain,
inet6addr_chain , netdev_chain这三条链,对应的注册,删除和通知函数(及其包裹函数)如下:

   1.Registration
int notifier_chain_register(struct notifier_block **list, structnotifier_block *n)

将新的节点n插入list指向的链表中
   Wrappers
inetaddr_chain: register_inetaddr_notifier

inet6addr_chain: register_inet6addr_notifier

netdev_chain: register_netdevice_notifier


   2.Unregistration
int notifier_chain_unregister(struct notifier_block **nl, structnotifier_block *n)

从链表nl中删除节点n
   Wrappers
inetaddr_chain: unregister_inetaddr_notifier

inet6addr_chain: unregister_inet6addr_notifier

netdev_chain: unregister_netdevice_notifier

 

   3.Notification
int notifier_call_chain(struct notifier_block **n, unsigned longval, void *v)

 

   通知链中的notifier_block元素按照优先级组织成一个链表,优先级相同的元素按照插入时间排序。对通知链的访问受到notifier_lock锁的保护,linux内核对所有的通知链只定义了一个锁,这并不影响性能,因为所有的notifier_call函数都是在系统引导或者模块加载的时候就已经注册了的,之后对通知链的访问都是只读的。

   notifier_chain_register函数的实现如下,它会遍历list,找到n的优先级对应的位置进行插入:

int notifier_chain_register(struct notifier_block **list, structnotifier_block *n)
{
   write_lock(&notifier_lock);

   while(*list)
    {
       if(n->priority >(*list)->priority)
       break;
       list= &((*list)->next);
    }
   n->next = *list;
   *list=n;
   write_unlock(&notifier_lock);
    return0;
}

 

五、向通知链发送通知消息

   通知消息通过notifier_call_chain函数(kernel/sys.c)产生,函数只是简单地按优先级次序调用通知链中注册的回调函数。值的注意的是,该函数是在调用它的进程的进程上下文中执行的,而回调函数也可以设计成把通知消息排进某处的队列,然后唤醒查看此通知消息的进程。

int notifier_call_chain(struct notifier_block **n, unsigned longval, void *v)
{
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;
}

其中对通知链调用的回调函数(notifier_call)的返回值可以是如下数值,它们被定义于/include/linux/norifier.h中:

   NOTIFY_OK
Notification was processed correctly.
   NOTIFY_DONE
Not interested in the notification. 不要用来替代NOTIFY_OK.
   NOTIFY_BAD
Something went wrong. Stop calling the callback routines for thisevent.
   NOTIFY_STOP
Routine invoked correctly. However, no further callbacks need to becalled for this event.
    NOTIFY_STOP_MASK
This flag is checked by notifier_call_chain to see whether to stopinvoking the callback
routines, or keep going. Both NOTIFY_BAD and NOTIFY_STOP includethis flag in their
definitions.

注意,对于同一个通知链,在同一时间,有可能不同的CPU会同时调用notofier_call_chain,而负责互斥访问和串行化的是回调函数

     

 六、网络子系统相关的通知链

   内核定义了至少10种不同的通知链,网络相关的通知链有:

inetaddr_chain:有关本地接口的ipv4地址的插入删除变更等的通知信息,ipv6使用类型的链inet6addr_chain

netdev_chain:有关网络设备注册状态的通知信息

 

通知链注册通常发生在感兴趣的内核组建初始化的时候,例如路由系统初始化时调用的函数ip_fib_init会注册两条相关的链:(code in /net/ipv4/fib_frontend.c)

static struct notifier_block fib_inetaddr_notifier = {
.notifier_call = fib_inetaddr_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_inetaddr_notifier(&fib_inetaddr_notifier);
}

 

 

七、定义自己的通知链

1.定义通知链的头节点

2.把某个事件发生时需要执行的回调函数以节点的形式插入链表

3.发生了感兴趣的时间,notifier_call_chain执行,链上注册的回调函数得到调用

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值