`
tempsitegoogle
  • 浏览: 869455 次
文章分类
社区版块
存档分类
最新评论

linux call chain

 
阅读更多

Linux中的通知链技术

在Linux内核中,各个子系统之间有很强的相互关系,某些子系统可能对其它子系统产生的事件感兴趣。为了让某个子系统在发生某个事件时通知感兴趣的子系统,Linux内核引入了通知链技术。通知链只能够在内核的子系统之间使用,而不能够在内核和用户空间进行事件的通知。

1数据结构:

通知链有四种类型:

·原子通知链(Atomicnotifierchains):通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,不允许阻塞。对应的链表头结构:

structatomic_notifier_head{

spinlock_tlock;

structnotifier_block*head;

};

·可阻塞通知链(Blockingnotifierchains):通知链元素的回调函数在进程上下文中运行,允许阻塞。对应的链表头:

structblocking_notifier_head{

structrw_semaphorerwsem;

structnotifier_block*head;

};

·原始通知链(Rawnotifierchains):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。对应的链表头:

structraw_notifier_head{

structnotifier_block*head;

};

·SRCU通知链(SRCUnotifierchains):可阻塞通知链的一种变体。对应的链表头:

structsrcu_notifier_head{

structmutexmutex;

structsrcu_structsrcu;

structnotifier_block*head;

};

通知链的核心结构:

structnotifier_block{

int(*notifier_call)(structnotifier_block*,unsignedlong,void*);

structnotifier_block*next;

intpriority;

};

其中notifier_call是通知链要执行的函数指针,next用来连接其它的通知结构,priority是这个通知的优先级,同一条链上的notifier_block{}是按优先级排列的。内核代码中一般把通知链命名为xxx_chain,xxx_nofitier_chain这种形式的变量名。

2运作机制

通知链的运作机制包括两个角色:

·被通知者:对某一事件感兴趣一方。定义了当事件发生时,相应的处理函数,即回调函数。但需要事先将其注册到通知链中(被通知者注册的动作就是在通知链中增加一项)。

·通知者:事件的通知者。当检测到某事件,或者本身产生事件时,通知所有对该事件感兴趣的一方事件发生。他定义了一个通知链,其中保存了每一个被通知者对事件的处理函数(回调函数)。通知这个过程实际上就是遍历通知链中的每一项,然后调用相应的事件处理函数。

包括以下过程:

·通知者定义通知链

·被通知者向通知链中注册回调函数

·当事件发生时,通知者发出通知(执行通知链中所有元素的回调函数)

被通知者调用notifier_chain_register函数注册回调函数,该函数按照优先级将回调函数加入到通知链中:

staticintnotifier_chain_register(structnotifier_block**nl,

structnotifier_block*n)

{

while((*nl)!=NULL){

if(n->priority>(*nl)->priority)

break;

nl=&((*nl)->next);

}

n->next=*nl;

rcu_assign_pointer(*nl,n);

return0;

}

注销回调函数则使用notifier_chain_unregister函数,即将回调函数从通知链中删除:

staticintnotifier_chain_unregister(structnotifier_block**nl,

structnotifier_block*n)

{

while((*nl)!=NULL){

if((*nl)==n){

rcu_assign_pointer(*nl,n->next);

return0;

}

nl=&((*nl)->next);

}

return-ENOENT;

}

通知者调用notifier_call_chain函数通知事件的到达,这个函数会遍历通知链中所有的元素,然后依次调用每一个的回调函数(即完成通知动作):

/**

*notifier_call_chain-Informstheregisterednotifiersaboutanevent.

* @nl: Pointertoheadoftheblockingnotifierchain

* @val: Valuepassedunmodifiedtonotifierfunction

* @v: Pointerpassedunmodifiedtonotifierfunction

* @nr_to_call: Numberofnotifierfunctionstobecalled.Don'tcare

* valueofthisparameteris-1.

* @nr_calls: Recordsthenumberofnotificationssent.Don'tcare

* valueofthisfieldisNULL.

* @returns: notifier_call_chainreturnsthevaluereturnedbythe

* lastnotifierfunctioncalled.

*/

staticint__kprobesnotifier_call_chain(structnotifier_block**nl,

unsignedlongval,void*v,

intnr_to_call, int*nr_calls)

{

intret=NOTIFY_DONE;

structnotifier_block*nb,*next_nb;

nb=rcu_dereference(*nl);

while(nb&&nr_to_call){

next_nb=rcu_dereference(nb->next);

#ifdefCONFIG_DEBUG_NOTIFIERS

if(unlikely(!func_ptr_is_kernel_text(nb->notifier_call))){

WARN(1,"Invalidnotifiercalled!");

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--;

}

returnret;

}

参数nl是通知链的头部,val表示事件类型,v用来指向通知链上的函数执行时需要用到的参数,一般不同的通知链,参数类型也不一样,例如当通知一个网卡被注册时,v就指向net_device结构,nr_to_call表示准备最多通知几个,-1表示整条链都通知,nr_calls非空的话,返回通知了多少个。

每个被执行的notifier_block回调函数的返回值可能取值为以下几个:

·NOTIFY_DONE:表示对相关的事件类型不关心

·NOTIFY_OK:顺利执行

·NOTIFY_BAD:执行有错

·NOTIFY_STOP:停止执行后面的回调函数

·NOTIFY_STOP_MASK:停止执行的掩码

Notifier_call_chain()把最后一个被调用的回调函数的返回值作为它的返回值。

3内核网络代码中对通知链的使用

内核网络部分使用的一些通知链:

·inetaddr_chain:ipv4地址变动时的通知链

·netdev_chain:网络设备状态变动时的通知链

网络代码中对通知链的调用一般都有一个包装函数,例如对netdev_chain的注册就是由register_netdevice_notifier()函数完成的:

intregister_netdevice_notifier(structnotifier_block*nb)

{

structnet_device*dev;

structnet_device*last;

structnet*net;

interr;

rtnl_lock();

err=raw_notifier_chain_register(&netdev_chain,nb);

if(err)

gotounlock;

if(dev_boot_phase)

gotounlock;

for_each_net(net){

for_each_netdev(net,dev){

err=nb->notifier_call(nb,NETDEV_REGISTER,dev);

err=notifier_to_errno(err);

if(err)

gotorollback;

if(!(dev->flags&IFF_UP))

continue;

nb->notifier_call(nb,NETDEV_UP,dev);

}

}

unlock:

rtnl_unlock();

returnerr;

rollback:

last=dev;

for_each_net(net){

for_each_netdev(net,dev){

if(dev==last)

break;

if(dev->flags&IFF_UP){

nb->notifier_call(nb,NETDEV_GOING_DOWN,dev);

nb->notifier_call(nb,NETDEV_DOWN,dev);

}

nb->notifier_call(nb,NETDEV_UNREGISTER,dev);

}

}

raw_notifier_chain_unregister(&netdev_chain,nb);

gotounlock;

}

这个函数主要完成两件事情:

1)把参数structnotifier_block*nb注册到netdev_chain通知链上;

2)系统中所有已经被注册过的或者激活的网络设备的事件都要被新增的这个通知的回调函数重新调用一遍,使设备更新到一个完整的状态。

dev_boot_phase定义如下,表示在启动阶段。

staticintdev_boot_phase=1;

例如,在启动阶段的网络模块初始化过程中,有一个调用过程inet_init()-->ip_init()-->ip_rt_init()-->devinet_init(),会注册一个ip_netdev_notifier通知链:

register_netdevice_notifier(&ip_netdev_notifier);

而ip_netdev_notifier定义为:

staticstructnotifier_blockip_netdev_notifier={

.notifier_call=inetdev_event,

};

inetdev_event()实现为:

staticintinetdev_event(structnotifier_block*this,unsignedlongevent,

void*ptr)

{

structnet_device*dev=ptr;

structin_device*in_dev=__in_dev_get_rtnl(dev);

ASSERT_RTNL();

if(!in_dev){

if(event==NETDEV_REGISTER){

in_dev=inetdev_init(dev);

if(!in_dev)

returnnotifier_from_errno(-ENOMEM);

if(dev->flags&IFF_LOOPBACK){

IN_DEV_CONF_SET(in_dev,NOXFRM,1);

IN_DEV_CONF_SET(in_dev,NOPOLICY,1);

}

}elseif(event==NETDEV_CHANGEMTU){

/*Re-enablingIP*/

if(inetdev_valid_mtu(dev->mtu))

in_dev=inetdev_init(dev);

}

gotoout;

}

switch(event){

caseNETDEV_REGISTER:

printk(KERN_DEBUG"inetdev_event:bug/n");

dev->ip_ptr=NULL;

break;

caseNETDEV_UP:

if(!inetdev_valid_mtu(dev->mtu))

break;

if(dev->flags&IFF_LOOPBACK){

structin_ifaddr*ifa;

if((ifa=inet_alloc_ifa())!=NULL){

ifa->ifa_local=

ifa->ifa_address=htonl(INADDR_LOOPBACK);

ifa->ifa_prefixlen=8;

ifa->ifa_mask=inet_make_mask(8);

in_dev_hold(in_dev);

ifa->ifa_dev=in_dev;

ifa->ifa_scope=RT_SCOPE_HOST;

memcpy(ifa->ifa_label,dev->name,IFNAMSIZ);

inet_insert_ifa(ifa);

}

}

ip_mc_up(in_dev);

/*fallthrough*/

caseNETDEV_CHANGEADDR:

if(IN_DEV_ARP_NOTIFY(in_dev))

arp_send(ARPOP_REQUEST,ETH_P_ARP,

in_dev->ifa_list->ifa_address,

dev,

in_dev->ifa_list->ifa_address,

NULL,dev->dev_addr,NULL);

break;

caseNETDEV_DOWN:

ip_mc_down(in_dev);

break;

caseNETDEV_CHANGEMTU:

if(inetdev_valid_mtu(dev->mtu))

break;

/*disableIPwhenMTUisnotenough*/

caseNETDEV_UNREGISTER:

inetdev_destroy(in_dev);

break;

caseNETDEV_CHANGENAME:

/*Donotnotifyaboutlabelchange,thiseventis

*notinterestingtoapplicationsusingnetlink.

*/

inetdev_changename(dev,in_dev);

devinet_sysctl_unregister(in_dev);

devinet_sysctl_register(in_dev);

break;

}

out:

returnNOTIFY_DONE;

}

在注册的时候传递的是NETDEV_REGISTER事件,所以在in_dev不为空时,只做switch语句中的一个动作:dev->ip_ptr=NULL;在in_dev为空时,调用inetdev_init()函数分配一个structin_device,此时如果是Loopback设备才有动作了。

4举例

这个例子由参考文章二给出。

在这里,写了一个简单的通知链表的代码。

实际上,整个通知链的编写也就两个过程:
首先是定义自己的通知链的头节点,并将要执行的函数注册到自己的通知链中。
其次则是由另外的子系统来通知这个链,让其上面注册的函数运行。

这里将第一个过程分成了两步来写,第一步是定义了头节点和一些自定义的注册函数(针对该头节点的),第二步则是使用自定义的注册函数注册了一些通知链节点。分别在代码buildchain.c与regchain.c中。
发送通知信息的代码为notify.c。

代码1buildchain.c
它的作用是自定义一个通知链表test_chain,然后再自定义两个函数分别向这个通知链中加入或删除节点,最后再定义一个函数通知这个test_chain链。

#include<asm/uaccess.h>
#include<linux/types.h>
#include<linux/kernel.h>
#include<linux/sched.h>
#include<linux/notifier.h>
#include<linux/init.h>
#include<linux/types.h>
#include<linux/module.h>
MODULE_LICENSE("GPL");

/*
*定义自己的通知链头结点以及注册和卸载通知链的外包函数
*/

/*
*RAW_NOTIFIER_HEAD是定义一个通知链的头部结点,
*通过这个头部结点可以找到这个链中的其它所有的notifier_block
*/
staticRAW_NOTIFIER_HEAD(test_chain);

/*
*自定义的注册函数,将notifier_block节点加到刚刚定义的test_chain这个链表中来
*raw_notifier_chain_register会调用notifier_chain_register
*/
intregister_test_notifier(structnotifier_block*nb)
{
returnraw_notifier_chain_register(&test_chain,nb);
}
EXPORT_SYMBOL(register_test_notifier);

intunregister_test_notifier(structnotifier_block*nb)
{
returnraw_notifier_chain_unregister(&test_chain,nb);
}
EXPORT_SYMBOL(unregister_test_notifier);

/*
*自定义的通知链表的函数,即通知test_chain指向的链表中的所有节点执行相应的函数
*/
inttest_notifier_call_chain(unsignedlongval,void*v)
{
returnraw_notifier_call_chain(&test_chain,val,v);
}
EXPORT_SYMBOL(test_notifier_call_chain);

/*
*initandexit
*/
staticint__initinit_notifier(void)
{
printk("init_notifier/n");
return0;
}

staticvoid__exitexit_notifier(void)
{
printk("exit_notifier/n");
}
module_init(init_notifier);
module_exit(exit_notifier);

代码2regchain.c
该代码的作用是将test_notifier1test_notifier2test_notifier3这三个节点加到之前定义的test_chain这个通知链表上,同时每个节点都注册了一个函数。

#include<asm/uaccess.h>
#include<linux/types.h>
#include<linux/kernel.h>
#include<linux/sched.h>
#include<linux/notifier.h>
#include<linux/init.h>
#include<linux/types.h>
#include<linux/module.h>
MODULE_LICENSE("GPL");

/*
*注册通知链
*/

externintregister_test_notifier(structnotifier_block*);
externintunregister_test_notifier(structnotifier_block*);

staticinttest_event1(structnotifier_block*this,unsignedlongevent,void*ptr)
{
printk("InEvent1:EventNumberis%d/n",event);
return0;
}

staticinttest_event2(structnotifier_block*this,unsignedlongevent,void*ptr)
{
printk("InEvent2:EventNumberis%d/n",event);
return0;
}

staticinttest_event3(structnotifier_block*this,unsignedlongevent,void*ptr)
{
printk("InEvent3:EventNumberis%d/n",event);
return0;
}

/*
*事件1,该节点执行的函数为test_event1
*/
staticstructnotifier_blocktest_notifier1=
{
.notifier_call=test_event1,
};

/*
*事件2,该节点执行的函数为test_event1
*/
staticstructnotifier_blocktest_notifier2=
{
.notifier_call=test_event2,
};

/*
*事件3,该节点执行的函数为test_event1
*/
staticstructnotifier_blocktest_notifier3=
{
.notifier_call=test_event3,
};

/*
*对这些事件进行注册
*/
staticint__initreg_notifier(void)
{
interr;
printk("Begintoregister:/n");

err=register_test_notifier(&test_notifier1);
if(err)
{
printk("registertest_notifier1error/n");
return-1;
}
printk("registertest_notifier1completed/n");

err=register_test_notifier(&test_notifier2);
if(err)
{
printk("registertest_notifier2error/n");
return-1;
}
printk("registertest_notifier2completed/n");

err=register_test_notifier(&test_notifier3);
if(err)
{
printk("registertest_notifier3error/n");
return-1;
}
printk("registertest_notifier3completed/n");
returnerr;
}

/*
*卸载刚刚注册了的通知链
*/
staticvoid__exitunreg_notifier(void)
{
printk("Begintounregister/n");
unregister_test_notifier(&test_notifier1);
unregister_test_notifier(&test_notifier2);
unregister_test_notifier(&test_notifier3);
printk("Unregisterfinished/n");
}
module_init(reg_notifier);
module_exit(unreg_notifier);

代码3notify.c
该代码的作用就是向test_chain通知链中发送消息,让链中的函数运行。

#include<asm/uaccess.h>
#include<linux/types.h>
#include<linux/kernel.h>
#include<linux/sched.h>
#include<linux/notifier.h>
#include<linux/init.h>
#include<linux/types.h>
#include<linux/module.h>
MODULE_LICENSE("GPL");

externinttest_notifier_call_chain(unsignedlongval,void*v);

/*
*向通知链发送消息以触发注册了的函数
*/
staticint__initcall_notifier(void)
{
interr;
printk("Begintonotify:/n");

/*
*调用自定义的函数,向test_chain链发送消息
*/

printk("==============================/n");
err=test_notifier_call_chain(1,NULL);
printk("==============================/n");
if(err)
printk("notifier_call_chainerror/n");
returnerr;
}

staticvoid__exituncall_notifier(void)
{
printk("Endnotify/n");
}
module_init(call_notifier);
module_exit(uncall_notifier);

Makefile文件(我修改了)

注意,记得先检查有没有安装当前linux版本的内核头文件:

obj-m:=buildchain.oregchain.onotify.o

CURRENT_PATH:=$(shellpwd)

LINUX_KERNEL:=$(shelluname-r)

KERNELDIR:=/usr/src/linux-headers-$(LINUX_KERNEL)

all:

make-C$(KERNELDIR)M=$(CURRENT_PATH)modules

clean:

make-C$(KERNELDIR)M=$(CURRENT_PATH)clean

运行(注意insmod要root权限)

make

insmodbuildchain.ko
insmodregchain.ko
insmodnotify.ko

这样就可以看到通知链运行的效果了

下面是我在自己的机器上面运行得到的结果(dmesg命令):

init_notifier
Begintoregister:
registertest_notifier1completed
registertest_notifier2completed
registertest_notifier3completed
Begintonotify:
==============================
InEvent1:EventNumberis1
InEvent2:EventNumberis1
InEvent3:EventNumberis1
==============================

5参考文章:

1http://hi.baidu.com/mczyh/blog/item/80f4200098588c087bec2ce8.html

2http://www.yuanma.org/data/2009/0427/article_3645.htm

3http://blog.chinaunix.net/u2/67414/showart_1993495.html

分享到:


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics