分层体系结构模式的应用

jonathan2004

分层体系结构模式的应用

author: jonathan

本文档的CopyRight归jonathan所有,可自由转载,转载时请保持文档的完整性。
/*----------------------------------------------------------------------------------------------------------------------------*/

说明:此帖首次发在驱网上,这里移植过来以备份

软件体系结构大的方向来看可分为 从混沌到结构、分布式系统、交互式系统和适应性系统等。

而分层模式属于从混沌到结构,他适用一项工作可以分解为多个子任务,其中每个子任务处于特定的抽象层次上。分层的应用很广泛,这里从Windows/linux中总结一下分层体系结构一些特点,思想为我所用。


1 使用条件: 一个需要分解的比较大的系统。比较著名的网络协议的iso 7层模型。

2 分层的标准:这个没有一定的标准,但是简单可以按照一个层次只做一件事的原则来分解。

3 设计要求:下层不可见上层,上层可见下层。也就是说下层暴露的接口可以为上层可见和使用,而上层暴露的接口不可以为下层可见和使用。

4 数据接口:一个统一的数据结构作为作为数据传递的介质,在所有层次中流转。如Windows中就是常见的IRPs, Linux网络中是sk_buff结构。

5 上面实现了从上向下单向数据流,可需要双工怎么办,也就是底层需要调用上层?如Windows驱动中底层硬件完成了操作,如何通知上层应用呢?注意,这里不是说可以通过轮训或者中断等等方法之类来调用上层的问题,而是如何调用上层模块的方式。

答案是采用回调的方式。

5.1   linux内核网络部分实现的层次回调

linux中是使用了模块注册钩子方式,固定了层次之间的关系。在底层收到事件需要传递到上层时,使用注册的钩子,把数据传递到上层模块。

   如网卡接收到数据后,需要传递给ip层,看看他是如何做的。

   先看一个数据结构:

struct packet_type {
__be16      type; /* 上层的类型,这里是ip协议:0x0800 */
struct net_device *dev; 
int      (*func) (struct sk_buff *,
           struct net_device *,
           struct packet_type *,
           struct net_device *); /* 这是关键所在。上层注册的回调函数 */

struct sk_buff    *(*gso_segment)(struct sk_buff *skb,
            int features);
int      (*gso_send_check)(struct sk_buff *skb);
void      *af_packet_priv;
struct list_head list;    /* 链路层保持的上层协议注册的结构链表。*/
};


   ip模块初始化:

static struct packet_type ip_packet_type = {
.type = __constant_htons(ETH_P_IP),
.func = ip_rcv, /* 接收回调函数 */
.gso_send_check = inet_gso_send_check,
.gso_segment = inet_gso_segment,
        };


static int __init inet_init(void)
        {
            ....

      dev_add_pack(&ip_packet_type); / * 链路层提供的接口,注册了ip层回调 */

      ....
}


    这样,在底层链路层收到tcp/ip数据后,根据链路层中协议字段值作为.type的检索值,调用func。这里的func就是ip_rcv.
   
    重点:由于这种模块间紧注册的关系,造成linux提供的只能以hook点方式来过滤数据。
      
5.2 Windows内核实现的层次回调

Windows内核的层次调用与linux不同:Windows通过irp动态完成回调的处理,也就是通过模块间的数据流同时携带了控制流,而不是如同linux通过模块间注册回调来完成,这是两种不同的思路。

重点: windows这种设计有它的灵活性,比如更加方便的动态添加过滤模块。

下面看看windows如何处理的。首先了解一下单个i/o 栈结构:


MajorFunction
MinorFunction
Flags
Control
Parameters (32 bytes)
DeviceObject
FileObject
CompletionRoutine      ---> 关键点, 回调函数地址
Context Pointer

通过IoSetCompletionRoutine设置回调:

        #define IoSetCompletionRoutine( Irp, Routine, CompletionContext, 
      Success, Error, Cancel ) {           PIO_STACK_LOCATION irpSp; 
              ASSERT( (Success) | (Error) | (Cancel) ? (Routine) != 
        NULL : TRUE ); 
          irpSp = IoGetNextIrpStackLocation( (Irp) ); 
          irpSp->CompletionRoutine = (Routine);   /* 回调函数哦 */
          irpSp->Context = (CompletionContext); 
          irpSp->Control = 0; 
          if ((Success)) { irpSp->Control = SL_INVOKE_ON_SUCCESS; } 
          if ((Error)) { irpSp->Control |= SL_INVOKE_ON_ERROR; } 
          if ((Cancel)) { irpSp->Control |= SL_INVOKE_ON_CANCEL; } 
}

       注意了,这个回调函数注册位置不是在本驱动所在的stack位置,而是下一个驱动的stack位置上。这样做,一方面是最底层驱动并不需要回调函数,他在完成任务后直接调用IoCompleteRequest();另一方面从空间利用的角度来考虑的,一般分配irp的驱动并不需要i/0 stack,而最低层驱动并不需要完成函数,所以把完成例程放到了下一个i/o栈。

i/o管理器在这里也起到关键作用。在完成回调中,i/o管理器分为两个阶段完成:线程无关处理和线程相关处理。线程无关处理主要是个分层驱动的回调接口的调用;而线程相关处理是把irp中数据copy给相关的线程。

6 总结

两种不同分层的实现:模块注册回调(注册完成即是层次确定之时)和数据流中注册回调(层次确定通过设备链动态完成),各有优势。在系统设计过程中综合考虑,采用不同的方案来解决。


回调函数中直接调用底层API导致的corrupted double-linked list错误

显示模块在释放内存时,提示corrupted double-linked list错误。怀疑是内存泄露,调查了一整天,后来发现某一个通信模块只要不调用其下层的某个API就没有问题。其原因是:该通信模块把对此API的调用写在了回调函数当中。

因为回调函数属于下层模块工作流的一部分,所以要避免在回调函数中直接调用下层的API。

设想一下,上下两层模块运行在不同的线程中,下层提供如下3个接口,FunctionA() 和 NotifyOK(),FunctionB(),其中NotifyOK函数由上层编写、由下层回调。工作流程是:当调用下层的FunctionA之后,要等待下层的回调通知NotifyOK(),之后再调用FunctionB()。而在下层的实现中,当FunctionB被调用时将释放某段内存,这样的话,如果上层在NotifyOK回调函数中调用FunctionB的话,便会产生这样的隐患:假如下层在通过NotifyOK通知上层之后使用了这段内存,便会出现内存泄露(因为其空间已经在NotifyOK回调中被提前释放了)。

安全的做法是:上层在收到NotifyOK之后,不是立即开始进行之后的工作流(调用FuntionB),而是以发消息的方式通知自己进行后续处理。

至于错误发生的时机,是因为释放内存时,堆管理器才会检查标记,发现内存泄露。

另外,还搜索了一下相关资料,了解到一些其它的信息。为了提高运算效率,通常分配的内存空间要大于申请空间(地址对齐)。当发生内存泄露,使用了申请外的空间,但没有超出实际分配空间时,有的早期的堆管理器并不会检查出来(操作系统的内存分配实现算法不相同).


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值