【IT168 技术文章】
1、引言
在国内,目前大型的软件工程中大都使用国外的软件测试平台,我们在测试工程中使用法国的LOGISCOPE测试平台对C语言开发的实时嵌入式软件进行测试。
由于实时嵌入式软件主要是对外部接口设备进行控制,而被送检的软件需要大量不同的硬件环境,在进行测试时,由于无法建立大量不同的接口设备的硬件仿真环境,每当执行输入输出指令时,程序便无法继续运行,因此,测试软件无法完整地覆盖整个程序的各个分支和语句。同时,被测试软件常常需要多个模块同时对某个外设进行共同测试,需要通过网络环境进行集中测试。
为了完成对各种实时嵌入式软件的测试工作,我们研制开发了基于网络的接口设备的软件仿真环境,使得用户程序运行时不再需要真正的接口硬件设备,而是与仿真软件进行通讯,交换数据,从而使得被测程序在脱离硬件的环境中顺利运行。本课题主要解决C语言和汇编语言混合编程中,把所有原来控制外设的输入输出指令都修改成与仿真软件进行通信的函数,同时,把原来由硬件触发的中断处理功能转由软件触发,以及通过网络环境完成多模块的集中测试。
本课题的意义在于建成一种通用的方法,使得不同软件开发的程序都能够使用这些功能模块与接口仿真软件进行通信,从而顺利地进行软件测试。
被测软件的运行环境绝大多数是基于DOS的,在仿真环境下进行测试时,由于原来的硬件环境不再存在,因此要求对被测软件进行相应的修改,使其在一个新的系统模型当中运行:原来与硬件进行I/O的指令,变成对仿真软件通讯动态链接库中函数的调用;由硬件触发的中断信号变成来自仿真软件的消息;更重要的是,原来由CPU负责调度的中断服务函数必须由新的模型来负责(因为不再有硬件中断信号),而由于DOS是一个单任务的操作系统,系统模型需要解决以下的问题:系统如何在程序运行的时刻检测到通过网络发过来的“中断信号”,如何中断当前的程序,去执行相应的中断服务函数,如何具有中断嵌套的功能。
基于以上要求,系统模型采用以下两种:
1、基于DOS的多任务调度系统模型
2、基于Windows多线程环境的系统模型
2.基于DOS环境的多任务调度模型
2.1实现原理
想要在DOS环境下实现多任务调度,必须使每个任务具有自己的堆栈。首先,栈用来实现任务切换,其次,它用来存贮任务的局部变量。
任务的切换是通过调用一个子程序来实现的,该子程序将堆栈指针由原来指向老任务的堆栈置为指向新任务的堆栈。程序返回时,新的任务指令指针(IP)就从栈中弹出。新任务就开始自动开始执行。
这个负责调度的子程序是通过时钟中断来定期触发的。当产生时钟中断时,需要做两件事情。首先,将标志(Flags)寄存器的内容压入栈中,其次,紧跟在指令指针(IP)之后,将CS(代码段)也压入栈中。最后,将中断服务子程序的段地址装入CS寄存器中,将偏址装入IP寄存器中.这样可以使ISR开始运行.中断返回时,CS,IP和Flags寄存器的内容自动弹出。为了实现任务的调度,新的时钟中断服务函数要完成两项工作。首先,它将除了栈指针(SP)和栈段(SS)寄存器之外的所有寄存器的值都存到栈中。(SP和SS的值存在另外的位置)。其次,它改变SS和SP寄存器的值,使它们指向另一个任务的堆栈。因此,当ISR返回时,新的任务的堆栈被弹出到各寄存器中,这使得机器的状态是针对新的任务的。由于IP中也是弹出的寄存器的值,因此新任务就开始执行。
在任务运行前,它必须按一定方式使堆栈初始化,这样使得当第一个时间片到来时,从栈中弹出的值能够使该任务从头开始运行。因此,任务的堆栈必须初始化并存放正确的寄存器值,同时指令指针也必须指向程序中的第一条指令。
2.2调度内核实现所用到的数据结构
interrupt 类型
当说明一个函数为interrupt类型时,它告诉编译器自动保存所有寄存器(sp和ss除外)的值,并且IRET指令终止该函数。每当进入到interrupt函数时,执行下列指令:
push ax, push bx, push cx,
push dx, push es, push ds
push si, push di, push bp
发生中断时,CPU自动将Flags,cs和IP寄存器压栈。我们将利用interrupt函数的栈的安排方式对要执行的任务的堆栈进行初始化。
Bp<-top of stack
DI
SI
DS
ES
DX
CX
BX
AX
IP
CS
PLAGS
任务中使用的寄存器的数据结构
typedef struct int_regs{
unsigned bp;
unsigned di;
unsigned si;
unsigned ds;
unsigned es;
unsigned dx;
unsigned cx;
unsigned bx;
unsigned ax;
unsigned ip;
unsigned cs;
unsigned flags;
};
此结构严格按照interrupt函数入口处堆栈的结构定义,初始化时,将结构中的代码段(CS),指令指针(IP)设置成构成此任务的函数的段地址和偏移地址。
用于任务管理的数据结构
struct task_struct{
unsigned sp;
unsigned ss;
unsigned char *stck;
unsigned LastTask;
unsigned IntNum;
unsigned Status;
}
当执行任务切换时,sp和ss保存当前栈指针和堆栈段地址,而任务调度程序将当前栈指针(_SP)和栈地址(_SS)设置成下一个将要执行的任务的栈指针和栈地址,当调度程序结束运行时,由于从栈中弹出的各个寄存器,包括代码段和指令指针都是指向新任务的,因此,新任务将自动运行,从而达到任务切换的目的。
2.3应用多任务调度功能实现对中断的仿真处理
由于被测试的用户源程序是工作在仿真环境下,无法接收硬件产生的中断信号从而自动执行相应的中断服务程序。因此,改编后的程序应该能够定期检查是否有从仿真软件发过来的中断信号,若有,则中断当前程序的执行,转入相应的中断服务函数执行。
因为用户的程序当中显式地设置中断向量,在改写用户的程序时,将每个中断服务函数入口都置于一个向量数组当中,此数组即为全局中断向量表,将任务号与相应的中断号一一对应。
当时钟中断触发任务调度程序时,调度程序首先检测由仿真软件发来的中断信号,如果有,则调度程序在当前运行的任务的数据结构中保存堆栈段段地址寄存器(_SS)、栈指针寄存器(_SP)、当前任务号,并将堆栈段段地址寄存器和栈指针寄存器设置成新的中断服务程序所在的任务的相应的值,使得当调度程序返回时,能够从新的任务开始运行。然后生成一个新的任务,在此任务当中调度对应于此中断号的中断服务程序这样就可以实现中断功能。
3.基于Windows多线程环境的系统模型
3.1实现原理
考虑部分由C语言编写的实时嵌入式程序经过适当的修改可以由VC编译后,在Windows环境中运行。因此可以利用Windows的多线程特性构造系统模型:把用户的主函数放在系统初始化时生成的一个主线程当中运行,同时,系统主函数监测由Socket端口发来的数据,如果是一个中断产生信号,系统主函数挂起当前正在执行的线程,新生成一个新的线程,并在新线程中执行相应的中断服务函数。
3.2系统的结构
由于CSocket类不能够由各个线程之间共享,而各个线程内部又要通过Socket端口接收和发送数据,因此应建立高效而又防止各线程之间冲突的机制,下面是系统的结构图:
当线程需要通过Socket接口收发数据时,首先检测/设置相应函数的信号量,若此资源不能使用,则线程被自动挂起。系统的主调函数负责轮询来自Socket接口的数据以及各个信号量资源,当资源可用时,唤醒相应的线程,完成其请求的操作。如果接收到中断产生信号,则生成新的线程,并挂起当前的线程,从而完成对中断请求的响应。
4.结束语
本文介绍了针对实时嵌入式软件的测试工具的研究与完善工作,根据不同的软件特点,构造了两种系统模型。通过实际的使用,都达到了比较满意的结果。这两种系统模型经过适当的修改,可以应用在其它语言编写的软件上,从而可以实现通用的模块功能。