模块区分
模块区分的”划”是规划的意思,意指怎样合理的将一个很大的软件区分为一系列功用独立的部分协作完结体系的需求。C言语作为一种结构化的程序规划言语,在模块的区分上首要依据功用(依功用进行区分在面向对象规划中成为一个过错,牛顿定律遇到了>相对论), C言语模块化程序规划需了解如下概念:
(1) 模块便是一个.c文件和一个.h文件的结合,头文件(.h)中是关于该模块接口的声明;
(2) 某模块供给给其它模块调用的外部函数及数据需在.h中文件中冠以extern关键字声明;
(3) 模块内的函数和全局变量需在.c文件最初冠以static关键字声明;
(4) 永久不要在.h文件中界说变量!界说变量和声明变量的差异在于界说会产生内存分配的操作,是汇编阶段的概念;而声明则仅仅告知包括该声明的模块在衔接阶段从其它模块寻觅外部函数和变量。如:
/*module1.h*/
int a = 5; /* 在模块1的.h文件中界说int a */
/*module1 .c*/
#include “module1.h” /* 在模块1中包括模块1的.h文件 */
/*module2 .c*/
#include “module1.h” /* 在模块2中包括模块1的.h文件 */
/*module3 .c*/
#include “module1.h” /* 在模块3中包括模块1的.h文件 */
以上程序的结果是在模块1、2、3中都界说了整型变量a,a在不同的模块中对应不同的地址单元,这个国际上历来不需求这样的程序。正确的做法是:
/*module1.h*/
extern int a; /* 在模块1的.h文件中声明int a */
/*module1 .c*/
#include “module1.h” /* 在模块1中包括模块1的.h文件 */
int a = 5; /* 在模块1的.c文件中界说int a */
/*module2 .c*/
#include “module1.h” /* 在模块2中包括模块1的.h文件 */
/*module3 .c*/
#include “module1.h” /* 在模块3中包括模块1的.h文件 */
这样假如模块1、2、3操作a的话,对应的是同一片内存单元。一个嵌入式体系一般包括两类模块:
(1)硬件驱动模块,一种特定硬件对应一个模块;
(2)软件功用模块,其模块的区分应满意低巧合、高内聚的要求。
多使命仍是单使命
所谓”单使命体系”是指该体系不能支撑多使命并发操作,微观串行地履行一个使命。而多使命体系则能够微观并行(微观上或许串行)地”一起”履行多个使命。
多使命的并发履行一般依赖于一个多使命操作体系(OS),多使命OS的中心是体系调度器,它运用使命操控块(TCB)来办理使命调度功用。TCB包括使命的当时状况、优先级、要等候的作业或资源、使命程序码的开端地址、初始仓库指针等信息。调度器在使命被激活时,要用到这些信息。此外,TCB还被用来寄存使命的”上下文”(context)。使命的上下文便是当一个履行中的使命被中止时,所要保存的一切信息。一般,上下文便是计算机当时的状况,也即各个寄存器的内容。当产生使命切换时,当时运转的使命的上下文被存入TCB,并将要被履行的使命的上下文从它的TCB中取出,放入各个寄存器中。
嵌入式多使命OS的典型比如有Vxworks、ucLinux等。嵌入式OS并非遥不行及的神坛之物,咱们能够用不到1000行代码完结一个针对80186处理器的功用最简略的OS内核,作者正准备进行此项作业,期望能将心得贡献给咱们。
终究挑选多使命仍是单使命办法,依赖于软件的体系是否巨大。例如,绝大多数手机程序都是多使命的,但也有一些小灵通的协议栈是单使命的,没有操作体系,它们的主程序轮番调用各个软件模块的处理程序,模仿多使命环境。
单使命程序典型架构
(1)从CPU复位时的指定地址开端履行;
(2)跳转至汇编代码startup处履行;
(3)跳转至用户主程序main履行,在main中完结:
a.初试化各硬件设备;
b.初始化各软件模块;
c.进入死循环(无限循环),调用各模块的处理函数
用户主程序和各模块的处理函数都以C言语完结。用户主程序最终都进入了一个死循环,其首选计划是:
while(1)
{
}
有的程序员这样写:
for(;;)
{
}
这个语法没有切当表达代码的含义,咱们从for(;;)看不出什么,只要弄理解for(;;)在C言语中意味着无条件循环才理解其意。下面是几个”闻名”的死循环:
(1)操作体系是死循环;
(2)WIN32程序是死循环;
(3)嵌入式体系软件是死循环;
(4)多线程程序的线程处理函数是死循环。
你或许会争辩反驳,大声说:”凡事都不是肯定的,2、3、4都能够不是死循环”。Yes,you areright,可是你得不到鲜花和掌声。实践上,这是一个没有太大含义的牛角尖,由于这个国际历来不需求一个处理完几个音讯就喊着要OS杀死它的WIN32程序,不需求一个刚开端RUN就自行了断的嵌入式体系,不需求不可思议发动一个做一点事就干掉自己的线程。有时分,过于谨慎制作的不是便当而是费事。君不见,五层的TCP/IP协议栈逾越谨慎的ISO/OSI七层协议栈大行其道成为事实上的规范?
常常有网友评论:
printf(“%d,%d”,++i,i++); /* 输出是什么?*/
c = a+++b; /* c=? */
等类似问题。面临这些问题,咱们只能宣布由衷的慨叹:国际上还有许多有含义的作业等着咱们去消化摄入的食物。实践上,嵌入式体系要运转到国际末日。
中止服务程序
中止是嵌入式体系中重要的组成部分,可是在规范C中不包括中止。许多编译开发商在规范C上添加了对中止的支撑,供给新的关键字用于标明中止服务程序(ISR),类似于__interrupt、#programinterrupt等。当一个函数被界说为ISR的时分,编译器会主动为该函数添加中止服务程序所需求的中止现场入栈和出栈代码。
中止服务程序需求满意如下要求:
(1)不能回来值;
(2)不能向ISR传递参数;
(3) ISR应该尽或许的言简意赅;
(4) printf(char * lpFormatString,…)函数会带来重入和功能问题,不能在ISR中选用。
在某项意图开发中,咱们规划了一个行列,在中止服务程序中,仅仅将中止类型添参加该行列中,在主程序的死循环中不断扫描中止行列是否有中止,有则取出行列中的第一个中止类型,进行相应处理。
/* 寄存中止的行列 */
typedef struct tagIntQueue
{
int intType; /* 中止类型 */
struct tagIntQueue *next;
}IntQueue;
IntQueue lpIntQueueHead;
__interrupt ISRexample ()
{
int intType;
intType = GetSystemType();
QueueAddTail(lpIntQueueHead, intType);/* 在行列尾参加新的中止 */
}
在主程序循环中判别是否有中止:
While(1)
{
If( !IsIntQueueEmpty() )
{
intType = GetFirsTInt();
switch(intType) /* 是不是很象WIN32程序的音讯解析函数? */
{
/* 对,咱们的中止类型解析很类似于音讯驱动 */
case xxx: /* 咱们称其为”中止驱动”吧? */
…
break;
case xxx:
…
break;
…
}
}
}
按上述办法规划的中止服务程序很小,实践的作业都交由主程序履行了。
硬件驱动模块
一个硬件驱动模块一般应包括如下函数:
(1)中止服务程序ISR
(2)硬件初始化
a.修正寄存器,设置硬件参数(如UART应设置其波特率,AD/DA设备应设置其采样速率等);
b.将中止服务程序进口地址写入中止向量表:
/* 设置中止向量表 */
m_myPtr = make_far_pointer(0l); /* 回来void far型指针void far * */
m_myPtr += ITYPE_UART; /* ITYPE_UART: uart中止服务程序 */
/* 相关于中止向量表首地址的偏移 */
*m_myPtr = &UART _Isr; /* UART _Isr:UART的中止服务程序 */
(3)设置CPU针对该硬件的操控线
a.假如操控线可作PIO(可编程I/O)和操控信号用,则设置CPU内部对应寄存器使其作为操控信号;
b.设置CPU内部的针对该设备的中止屏蔽位,设置中止办法(电平触发仍是边际触发)。
(4)供给一系列针对该设备的操作接口函数。例如,关于LCD,其驱动模块应供给制作像素、画线、制作矩阵、显现字符点阵等函数;而关于实时钟,其驱动模块则需供给获取时刻、设置时刻等函数。
C的面向对象化
在面向对象的言语里边,呈现了类的概念。类是对特定数据的特定操作的调集体。类包括了两个领域:数据和操作。而C言语中的struct仅仅是数据的调集,咱们能够运用函数指针将struct模仿为一个包括数据和操作的”类”。下面的C程序模仿了一个最简略的”类”:
#ifndef C_Class
#define C_Class struct
#endif
C_Class A
{
C_Class A *A_this; /* this指针 */
void (*Foo)(C_Class A *A_this); /* 行为:函数指针 */
int a; /* 数据 */
int b;
};
咱们能够运用C言语模仿出面向对象的三个特性:封装、承继和多态,可是更多的时分,咱们仅仅需求将数据与行为封装以处理软件结构紊乱的问题。C模仿面向对象思维的意图不在于模仿行为自身,而在于处理某些情况下运用C言语编程时程序全体框架结构涣散、数据和函数脱节的问题。咱们在后续章节会看到这样的比如。
总结
本篇介绍了嵌入式体系编程软件架构方面的常识,首要包括模块区分、多使命仍是单使命选取、单使命程序典型架构、中止服务程序、硬件驱动模块规划等,从微观上给出了一个嵌入式体系软件所包括的首要元素。
请记住:软件结构是软件的魂灵!结构紊乱的程序面目可憎,调试、测验、保护、晋级都极度困难。