模块间的数据交互
zchs_protocol | ||
vince | pipe | grab |
对于zchs_protocol协议模块和下面的三个运动部件模块之间的交互问题。
实习方式
- 变量
无系统:跨模块的全局变量
有系统:信号量等
- 函数接口
方式选择优缺点说明
关于变量
- 无系统
例如,在zchs_protocol中定义serial_send_state:0没有数据发送,1有数据发送。
需要分别在三个运动部件模块中分别置位此标志变量,然后在zchs_protocol中实现发送函数(检测此标志,然后发送)。
但是这这其中存在的问题是,如果vince模块已经将serial_send_state置1,有vince的数据要发送。那么又有pipe或者grab模块也要置位此位,要申请串口发送数据。那么zchs_protocol就不知道要发送谁的了。说白了,就是需要明确三个底层模块的发送顺序。
发送顺序又有两种实现方式:变量和逻辑控制
变量最简单的就是,每个运动模块都在自己模块内置位,清位serial_send_state。每次置位serial_send_state前,都检测是否为1,如果为1,则放弃此次发送,等待下一次轮询到自己模块有没有数据要发送。 - 有系统
利用信号量,实现上面逻辑业务逻辑
关于函数接口
在zchs_protocol中定义一个serial_send()接口函数,由三个运动部件调用。
但是一样面对,不知道vince调用此函数的时候,pipe和grab有没有调用。这样的问题。
同样需要标志变量或者逻辑控制,实现**“发送接口”**这个共享资源问题。
其实,裸机没有任务中断调度,已经通过逻辑控制解决了这个问题。即,不可能vince在通过zchs_protocol发送数据的时候,pipe和grab也有数据同时申请发送。
但是,由系统的时候,就需要依旧需要信号量去解决这个问题。
综上,对于变量,裸机和系统都适用。
对于接口函数,裸机适用,系统不适用。
所以,变量实现方式更占优势。
优势:适用普遍,有利移植
zchs_protocol uint8_t s_state=0; if(s_state==1) { serial_send(); s_state=0; } |
vince if(v_sate==1) { if(s_state!=1) {s_state=1;v_state=0;} } |
pipe if(p_sate==1) { if(s_state!=1) {s_state=1;p_state=0;} } |
grab if(g_sate==1) { if(s_state!=1) {s_state=1;g_state=0;} } |
zchs_protocol
poll()
{
check_sub_send();
switch(state)
{
ready:break;
recive:break;
send:break;
idle:break;
}
}
上面的描述虽然有些道理,但是经过一些日子的磨炼,现在又感觉有些肤浅。这是有了新的感悟。现描述如下:
模块的堆叠
我们经常在一些让人崇拜的官网上看到下面这样的软件图解:
APP | ||
drive_a | drive_b | drive_c |
APP_A | APP_B | APP_C |
drive |
这是软件的整体划分,由下而上的堆叠,这个我自然很容易了解了,其作用也是一样很容易理解了。但是,若想切身的体会却是不容易。应该还是要去大公司工作,并且有着坚实的基础,才会有着切身的体会。
体会就是:即使上层领导根据项目需求,划分好模块了,那么又如何保证软件的书写、构造完美遵循这个堆叠结构呢?
这里面有大量的工作。我技术不够好、层次不够高,也是说不好的。但是却有着下一些切身体会。
1,模块之间的交互方式:数据交互、函数接口、全局变量。
这些实现方式,就像数学中的“加减乘除”是基本的要素,需要我们自己的搜寻、借鉴。有哪些方式实现、各个实现方式能达到的效果、其优缺点又是什么都要自己平时一一积累总结。就是模拟电路中,运放电路的基本电路”加法、减法、乘法、除法、积分、微分、vi、iv、低通、高通、带通、跟随、锁相环、、、”等一样。这都是你以后设计电路的基础电路,有了他们,就是数学中你学会了加减乘除,才能去用它们解决各种问题,他们是基础啊!这个基础知识不行,那还谈什么。也像画家手中的“笔、纸、赤、橙、黄、绿、金、蓝、紫墨水”,有了它们,你才能做画,是一样的道理。
然而市面上的教材,却少有提及这个的,教这个的。其实,我一直认为最好的教材,莫过于汇聚了各个高手的操作系统源码。所以,我把整理这些基础素材的来源,总是分为“带系统”和“不带系统”两种实现方式。
带系统的模块间交互方式:
其实网上都有。这里只是告诉大家把这些思想联系起来。
数据交互方式:队列、邮箱。
函数接口:注册(操作集的指针赋值)。
就是rt_thread和Linux中驱动架构中的操作集的函数接口赋值。
struct file_operation fops =
{
.open = xxx_open;
.close = xxx_close;
.read = xxx_read;
.write = xxx_write;
.probe = xxx_probe;
};
全局变量:信号量,互斥锁、事件。
不带系统的模块间交互方式:
函数接口:
在“model_a.c”的“model_a.h”中声明对外的
void a_open(void);
void a_close(void);
void a_read(void);
…
或者采用extern关键字修饰,在合适的地方声明。
者两种方式,前者对所有上层模块开放,并且include之后夹带其他通用对外开放信息。所以适合带有common属性。而extern则只需要在需要的上层文件中声明就可,并且不带有.h文件中的其他对外common属性。
对于全局变量和数据交互,也是一样考虑。
2,模块堆叠需要遵守的一些规则和考虑
- 机制和策略(即“输入”和“输出”)
- 认清模块在软件栈中的位置-
- 向下依赖, 不要向上依赖
- 避免同级依赖