- 浏览: 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> |
代码2regchain.c
该代码的作用是将test_notifier1test_notifier2test_notifier3这三个节点加到之前定义的test_chain这个通知链表上,同时每个节点都注册了一个函数。
#include<asm/uaccess.h> |
代码3notify.c
该代码的作用就是向test_chain通知链中发送消息,让链中的函数运行。
#include<asm/uaccess.h> |
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 |
这样就可以看到通知链运行的效果了
下面是我在自己的机器上面运行得到的结果(dmesg命令):
init_notifier |
5参考文章:
1http://hi.baidu.com/mczyh/blog/item/80f4200098588c087bec2ce8.html
相关推荐
linux system call quick reference
系统调用 system call linux system call linux system api
Adding a system call in linux
讲解linux内核 系统调用 部分经典讲义,通俗易懂,不得多得 优秀 的内核学习资料
ODBC解决Call to undefined function odbc_connect
This document gives you steps to add a system call in linux kernel.
对于学习linux系统编程的人来说,这无疑是一个很好的手册资料,此资源包含两个资料,是linux-system-call-function 的Reference 和参考资料。
Linux系统调用快速查看 所有系统调描述 所有系统调用对应源码位置
os实验报告 linux编译内核, 添加一条helloworld的system call. 英文写的, 不过写得很简单啦, 每一步都有截图
错误现象:(semop函数调用,strerror(errno)输出结果)Interrupted system call平台:RedHat LinuxLINUX文档关于EINTR的描述是这样子的: While blocked in this system call, the process caught a signal.UNIX文档...
模拟游戏找CALL,F8CALL01-04
独立团CALL测试工具,找CALL必备,写挂工具,逆向工程
很好用的CALL调试测试工具,无代码注入。
1、性能指标性能指标概念:高并发=>吞吐响应快=>延时该概念是从应用负载的角度出发: Application o Libraries , System Call - LinuxKernel Drive与之对应的是系统资源视角出发: Drive - Linux Kernel ,System Call o...
linux 系统函数调用api文档,方便系统函数查阅,c语言api接口
python库。 资源全名:chain_call-0.0.2-py3.5.egg
一个简单的打印程序调用栈的例子 .... <br>http://topic.csdn.net/u/20080804/15/623a4355-cfeb-4241-8fba-022fff8facf4.html
资源分类:Python库 所属语言:Python 资源全名:chain-call-0.1.1.zip 资源来源:官方 安装方法:https://lanzao.blog.csdn.net/article/details/101784059
Callus 95模拟器是一个十分好用的街机游戏模拟器软件,也是当前最好的CPS1模拟器,在一些老机器上都能运行,效果也不错,它最大的优点就是可以模拟我们小时候玩的三国志、街头霸王等游戏,当前玩玩以前的小游戏,确实...
linux 2.6.32下,修改system_call sysenter_do_call-隐藏文件目录。