c语言模块化编程思想(二)

模块的设计原则

高内聚低耦合是模块设计的基本原则。模块的内聚度是指模块内的各个元素的关联,交互程度。从功能角度上看,就是各个模块在事先各自功能的时候,要自己的事情自己做,自己的功能自己事先,尽量不麻烦其他模块。

一个模块想要事先高内聚,首先模块的功能要尽可能单一,一个功能由一个模块实现,这样才能体现模块的独立性,进而实现高内聚性。在模块实现的过程中,遵循“自己动手,丰衣足食”的基本原则,尽量调用本模块实现的函数,减少对外部函数的依赖。因为这样可以进一步提高模块的独立性,提高模块的内聚度。

模块耦合指的是模块间的关联和依赖关系,包括:调用,控制,数据传递等。模块间的关联越强,其耦合度就越高,模块的独立性就越差,其内聚度随之降低。不同的模块有不同的关联方式,也有不同的耦合方式。

  • 非直接耦合:两个模块之间没有直接的联系
  • 数据耦合:通过参数来交换数据
  • 标记耦合:通过参数(标记变量)传递记录信息
  • 控制耦合:通过标志,开关,名字等,控制另一个模块
  • 外部耦合:所有模块访问同一个全局变量。

设计模块时,要尽量降低模块的耦合度。方法:

  • 接口设计:隐藏不必要的接口和内部数据类型,模块引出的API封装在头文件中
  • 全局变量:尽量少用,
  • 模块设计:尽可能独立存在,功能单一明确,接口少而明确
  • 模块依赖:模块之间最好是单向调用,那就是上下依赖,禁止相互调用。

简单说:模块功能划分要清晰,接口要明确,有明确的输入和输出,模块间的耦合性小。通过坚持这些原则,不断对自己的代码进行重构和迭代,才可以设计出高质量的代码和更加易于维护的系统架构。

被误解的关键字goto

一般来讲,任何复杂的程序逻辑都可以通过顺序,分支,循环这三种基本程序结构组合来实现,这也是不推荐使用goto的原因。goto的语法使用源自汇编世界。

适合goto语法的情况:
有条件跳转的特性有时候会大大简化程序的设计。如果有多个错误出口的函数,我们可以使用goto将函数的出错指定一个统一的出口,统一处理,这样可以是函数的结构更加清晰。

在多重循环的程序中,如果我们想从最内层的循环直接跳出,则需要多次使用break和return,层层退出才能带到预期目的。使用goto无条件跳转,可以一步到位。

goto用法并没有被抛弃,在linux内核源码中,有很多这样的使用案例。

goto的缺点:

  • 只能往前跳,不能往回跳。
  • goto只能在同一函数内跳转,函数内goto标签的位置也有一定讲究,goto标签一般在函数体内两端不同逻辑功能代码的交界处,用来区分函数内的模块化设计和逻辑关系。

模块间通信

系统内各个模块之间通过各种耦合的方式进行通讯。

全局变量

各个模块共享全局变量是各个模块之间进行数据通信最简单的方式。一个全局变量具有文件作用域(就是这个全局变量仅在该文件中有效), 但是可以使用extern关键字将全局变量的作用域扩展到不同的文件中,然后各个模块就可以通过全局变量进行通信了。
在这里插入图片描述

为了减少使用全局变量进行通讯带来的外部耦合带来的耦合性,我们可以基于上述方案进行改进:把对全局变量的直接访问修改为通过函数接口间接访问。就像类的私有成员一样,该全局变量只能在一个模块中创建或直接修改,如果其他模块想要访问这个全局变量,只能通过引出这个函数读写接口进行访问。

// module.h
void val_set(int value);
int val_get(void);

//module.c
int global_val = 10;
void val_set(int value){
    global_value = value;
}

int val_get(void){
    return global_val;
}

在以上的示例中,对于module模块中定义的全局变量global_val,其他模块想要对其进行访问时,不再通过变量名直接访问,而是通过module封装的val_set()和val_get()接口进行访问。大多数,多任务情况下,需要注意全局变量的互斥访问。

Linux内核源码中使用了很多的全局变量。

回调函数

一个系统的不同模块可以通过数据耦合,标记耦合的方式进行通信,即通过函数调用过程中的函数传递,返回值来实现模块间的通信。

// module.h
int send_data(char *buf, int len);

//module.c
int send_data(char *buf, int len){
    char data[100];
    int i;
    for (i = 0; i<len; i++){
		data[i] = buf[i]
	}
	return len;
}

//main.c
#include<stdio.h>
#include"module.h"

int main(void){
char buffer[10] = {1,2,3,4,5,6,7,8,9,0};
int return_data;

return_data = send_data(buffer, 10);
printf("send data len:%d\n", return_data);
return 0;
}

上述代码将main.c模块中buffer中的数据传递到module.c模块并进行打印,同时通过send_data()函数的返回值将数据传递的长度信息从module.c模块反馈给main.c模块。

这种通讯方式就是一种单向调用的,这是系统模块化设计的体现,每一层的模块都对下一层的封装,并留出API给上一层使用。每一层模块只能主动调用下一层提供的API,然后将自己封装成API供上一层调用。

可以通过回调函数,实现底层的模块主动的与上一层模块进行通信。我们在编写程序实现一个函数时,通常会直接调用底层模块的API函数或者库函数。如果反过来,我们写一个函数,让系统直接调用该函数(这里的系统指?),这个过程被称为回调callback, 这个函数被称为回调函数 callback function.
在这里插入图片描述

通过回调函数的设计,两个模块之间可以实现双向通信。

回调函数在实际编程中被广泛使用,如linux设备驱动模型框架,GUI窗口编程等。

异步通信

无论模块接口,回调函数都是阻塞式同步调用,占用CPU资源。异步通信。

概念:同步,异步,阻塞,非阻塞

  • 装一壶水,插上电,什么也不干,等待水烧开:同步,阻塞
  • 装上一壶水,插上电,去看电视,5分钟去看一下水烧开了没有:同步,非阻塞
  • 装上一壶水,插上电,躺床上什么也不干,等水烧开听到沸腾的声音,然后去倒水:异步,阻塞
  • 装上一壶水,插上点,去看电视,然后等水烧开听到沸腾的声音,然后去倒水:异步,非阻塞。

也就是CPU在访问一个资源是,如果资源没有被准备好,需要等待,CPU什么也不敢,等待资源准备完成就是同步通信;如果CPU去干其他的事情,等资源准备好了,然后通知CPU,CPU再来访问就是异步通信。

也就是说:同步调用会一致占用CPU的资源,让CPU的长时间处于等待状态,降低系统的性能。而异步通信可以解放CPU资源,在等待的时间内,cpu可以去做其他的事情,提高CPU的利用率,常用的异步通信如下:

  • 消息机制:
  • 事件驱动机制:状态机,GUI,前端等
  • 中断
  • 异步回调

Linux操作系统中,各个模块会采用不同的异步通讯方式进行通信。例如

  • notify机制进行通讯
  • AIO,netlink进行通信
  • 用户模块之间异步通信更多:操作系统支持的管道,信号,信号量,消息队列,socket,PIPE,FIFO等进行异步通信。

模块进阶设计

跨平台设计

不同版本的软件运行的平台不一样,操作系统也不一样,编写程序需要考虑跨平台设计。主流操作系统:

  • windows系列
  • linux/unix系列:ios,mac os, linux(x86, arm, mips), android
  • 嵌入式RTOS: FreeRTOS, RT-Thread…

不同的操作系统,提供的api或系统调用接口不一样,应用程序在调用这些接口时,考虑跨平台设计,需要对这些接口进行封装。

C语言本身是跨平台的,因此C语言标准和C标准库里定义的函数接口也是与平台无关的。同一个C标准库函数,不同的平台下可能会通过不同的系统调用接口实现,但是留给应用程序的接口是C语言标准规定的。因此在编写程序时,需要尽量考虑C标准库函数,而不是直接使用操作系统的系统调用接口。

还有:不同的架构,不同位宽,大端,小端,内存对其,不同数据类型的字长等。

框架

框架是一个可扩展的应用程序骨架。框架是一个半成品,对于嵌入式开发领域来说,开发板就是一个框架,如果你想做一个MP3播放器,整个产品的开发流程为:硬件电路设计,画板子,移植操作系统,开发驱动,开发应用程序实现mp3播放。将重复的步骤进行分离,抽象,模块化,通用的模块下沉,慢慢迭代成平台,就可以迭代为开发板了。

开发一个网站时的框架:PHPCMS,Django,z-Blog。人工智能领域开发框架:Caffe,TensorFlow。Java:Spring框架。嵌入式:FFmpeg框架。

使用框架能够快速开发产品,提高工作效率,还能提高软件开发质量,降低软件开发的门槛和人员要求。 框架是一个公司长期技术积累的结晶,是公司最核心的竞争力。

AIoT时代的模块化编程

物联网技术和人工智能的发展,嵌入式系统变得越来越复杂:嵌入式设备具备联网功能,接入云端,将感知的数据传入云服务器;第二个,在边缘侧开始支持人工智能,将以前由云端进行模型训练的工作转移到不同的设备节点上。越来越多的协议栈,组建,服务集成到嵌入式系统中。

不同的通讯协议需要不同的通信模块,在硬件层面,通过硬件平台的通用接口,可以将wifi,蓝牙,4G做成独立的通信模组,适配不同的开发板和平台。用户在开发应用产品时,可以根据不同的需求选择不同的通信方式,选择不同的硬件模块。
在这里插入图片描述

总结

提示:这里对文章进行总结:

本文只是笔者粗浅的总结。有兴趣可以阅读<嵌入式c语言自我修养>

  • 60
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值