zedboard第二十课(Multi-Layer Data Processing)

协议栈是最常见的Multi-Layer Data Processing的程序架构。

首先要清楚,协议栈是如何运行的。
协议栈不是由用户程序调用的。而是由中断ISR调用的。
所以,协议栈是和用户APP复用CPU的。甚至协议栈的每个Layer都是复用CPU的,更甚至协议栈的每个Layer的每个Department都是复用CPU的。
在一个Multi_Layer协议栈中,每个Layer都由不同的中断来启动它的运行时机,甚至,每个Layer中的每个Department,都由不同的中断来启动它的运行时机。
从这个角度看,我们可以把协议栈理解为,是多进程的软件系统,或者多任务的软件系统。

Multi_Process和CallingStack有着很大的区别。
在CallingStack的程序架构中,函数和函数之间,有着明确的运行先后顺序,调用者wait for return。类似于调用者此时是被挂起的(suspending)。
首先回顾一下关于函数调用的生命周期的相关概念。
CallingLife的存续标准,是CallingStackFrame的存续。当调用创建时,StackFrameCreat,当调用返回时,StackFrameDistroy。每个函数调用被创建时,只能在某一个CallingStack中被Create,这个CallingStack则Growup。每个CallingStack只有StackTop是活动的,ActiveStackFrame,此时,调用者的StackFrame是NonActiveStackFrame,调用者处于suspending状态。
由于只有栈顶是活动的,所以,一个CallingStack,就是一个线程(Thread),不管这个CallingStack有多深。
所以,如果软件系统是Multi_Process的,那么,就必须要有多个CallingStack,才能支持多个Thread的存在。
函数调用被创建的目的,是为了以消耗CPU时间资源为代价,完成特定的任务。函数调用要完成的任务,一般有如下几种:
1)局部数据处理。Local Data Process。将StackFrame中的数据计算后,存放到RetVal中。
2)外部数据处理。External Data Process。用StackFrame中的指针,查找到外部数据,计算后,利用指针存放到External Data中。
3)全局数据处理。Global Data Process。用Text Instruction中的指针,查找到全局数据,计算后,利用指针存放到Global Data中。

对于单进程软件系统,由于只有一个CallingStack,所以多个函数调用之间,不会出现数据访问竞争(Data Access Competition)。因为每个时刻,都只有一个函数调用是活动的。
但是对于多进程软件系统,情况变得复杂。不同的CallingStack中的函数调用之间,将会出现数据访问竞争(Data Access Competition)。因为有可能一个函数调用A访问数据的时候,CPU被另一个函数调用B所抢占并访问了同一个数据,而等到CPU被交还给函数调用A时,A再继续访问数据,就出现了数据一致性问题(Data Consistency Problem)。B和A并不在同一个CallingStack,所以A并不知道B何时会执行。
解决多进程的数据一致性问题的方法,就是额外增加一些公共辅助数据,用来记录目标数据的访问状态。
这是唯一的办法,通过让多进程访问公共的外部数据,使多进程之间能够通信。
这是SOD设计思想的优势所在,一个设计良好的数据布局,才能够得到期望的数据结果。
有了设计良好的数据布局,下一步就是考虑函数的功能划分和角色分工,最后得到软件系统的函数布局。函数布局是静态的角度看待软件的重要方式。每个函数由于角色分工的不同,处于程序架构中的不同位置。
分层架构,是常见的软件架构。
从静态的角度看,函数处于不同的Layer,处于不同的Department,有着不同的角色分工,完成不同的任务。
从动态的角度看,各个不同Layer不同Department的函数将在不同的Thread中被创建调用,他们从属于不同的CallingStack。与外部的通信,借助于外部数据的指针,或者全局数据的指针。

在引入了动态链接(Dynamic Link)之后,情况变得更为灵活,但是也更为复杂。函数指针(FunctionPointer)的存在,是动态链接的基础。它使得常规的对指针这个概念的理解,发生了变化。指针可以是指向Data的,也可以是指向Text Instruction的。
这就是函数接口的物理基础(Function Interface)。函数接口是数据布局中的一个特殊的数据结构体。当函数调用在访问函数接口时,并不是为了取回目标数据,而是为了创建一个函数调用。
函数接口是一个结构体,或者至少是一个DataSet。除了包含函数指针以外,还要包含被调用的函数所需要的参数列表。有了函数入口和参数列表,才能创建一个函数调用。例如,我们常用的Callback和CallbackRef,就是函数接口的具体应用。
Callback总是由系统中的Routine来创建函数调用的,它并不在MainThread中被创建函数调用,为了能让其他的Routine在运行时找到Callback,我们在数据布局的设计时,一定会预留好函数接口,使得Routine能够通过函数接口,创建Callback的函数调用。而MainThread中需要做的,就是配置好函数接口。例如,常见的ConnectCallback,SetupCallback等。

程序架构的合理性,总是和数据结构的合理性,紧密相关。
不同的Layer,不同的Department,所需要使用的数据结构体是不同的,我们需要为他们分别设计结构体,即控制块体系。
它们在不同的Thread中执行任务,需要数据通信,我们需要为他们设计用于数据通信的结构体,即业务载荷体系。
它们相互之间,并不知道对方的存在,只知道自己的StackFrame中存在的数据对象指针,我们需要为他们设计用于数据对象相互关联索引的索引体系。
这些问题,都是数据布局要解决的问题。

驱动模块是典型的分层程序架构。
通常包含硬件访问层,协议处理层,应用接口层。
在输入的方向上,硬件访问层,负责将外设上的数据接收,处理后,将本层的业务载荷转储到载荷缓冲区并提交给协议处理层,协议处理层对载荷缓冲区的业务载荷进一步处理,并将本层的业务载荷转储到载荷缓冲区并提交到应用接口层,应用接口层对载荷缓冲区中收到的业务载荷进一步处理,转储给用户的缓冲区。
在输出的方向上,应用接口层,负责接收用户缓冲区的数据,处理后,提交到协议处理层的载荷缓冲区,协议处理层对载荷缓冲区的业务载荷进一步处理,并转储到下一层的业务载荷区,硬件访问层将载荷缓冲区的业务载荷进一步处理,通过外设发送数据。
整个处理过程中,我们可以清晰的看到,层与层之间,通过载荷缓冲区进行业务载荷的传递。从而将各个层独立开,他们可以被放置在不同的CallingStack中,以不同的Thread的形式运行,实现并行化。

SOD设计思想是程序设计的出发点。所以,首先要设计出整个架构中的关键结构体。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值