整个程序方案中,核心的代码是实现系统的事件驱动功能,被定义成一个C++类如下:
#if !defined(_CMDRIVE_H)
#define _CMDRIVE_H
#ifdef __cplusplus
#define __CPPARGS ...
#else
#define __CPPARGS
#endif
#include < dos.h >
enum CMD { NOP, TASK1, TASK2, TASK3, EXIT }; // 可以根据应用定义更多的命令
#define MaxCmdStack 400 // 定义系统任务队列的长度
#define PARLEN 14 // 每个命令所带参数的长度
class TaskQueue
{
static unsigned int PutIdx; // 通过2个index的操作,使CmdBuf[ ]成为
static unsigned int GetIdx; // 逻辑上的环型buffer,即FIFO数据结构
static CMD CmdBuf[MaxCmdStack];
static char CmdPar[MaxCmdStack][PARLEN];
static struct time OldTime;
static struct date OldDate;
static unsigned int TickCount; // 定时计数
static unsigned int TickSize; // 确定最小的定时间隔,可变,初值为0
static void interrupt INT1C_Handler(__CPPARGS); // 通过INT 1C实现定时任务发生器
static int ISR_PushCmd( CMD NewCmd, char* pPar=NULL ); // 中断程序中使用
public:
TaskQueue( );
~TaskQueue( );
CMD GetCmd( char* pPar=NULL ); // 读取当前队列中的命令
int PushCmd( CMD NewCmd, char* pPar=NULL ); // 填入新的命令到系统任务队列
void StartQueue( ); // 启动定时任务发生器
void StopQueue( ); // 关闭定时任务发生器
};
extern class TaskQueue CmdQueue; // 在cmdrive.cpp中定义的类变量实例
#endif
在TaskQueue类的定义中有3个核心API函数,用于实现任务队列和定时任务发生:
CMD TaskQueue::GetCmd( char* pPar ) // 从FIFO读取命令
{
CMD CmdCode;
if( GetIdx != PutIdx )
{
disable( );
CmdCode = (CMD)CmdBuf[GetIdx];
if( pPar != NULL ) memcpy( pPar, CmdPar[GetIdx], PARLEN );
GetIdx = ( GetIdx + 1 ) % MaxCmdStack;
enable( );
return CmdCode;
}
return NOP;
}
// return = -1: command aborted
// = 0: command pushed
int TaskQueue::PushCmd( CMD NewCmd, char* pPar ) // 把命令填入任务队列
{
unsigned int Idx;
if( GetIdx == 0 ) Idx = MaxCmdStack - 1;
else Idx = GetIdx - 1;
disable( );
if( PutIdx == Idx ) return -1; // 表明队列已满
CmdBuf[PutIdx] = NewCmd; // 填入命令码
if( pPar == NULL ) memset( CmdPar[PutIdx], 0, PARLEN ); // 填入参数
else memcpy( CmdPar[PutIdx], pPar, PARLEN );
PutIdx = ( PutIdx + 1 ) % MaxCmdStack; // 序号按模加1
enable( );
return 0;
}
环形缓冲区的核心是使用了一块连续的内存,并定义了两个Index序号:一个是记录往缓冲区填数的PutIdx;一个是记录从缓冲区取数的GetIdx。置数和取数是两个完全异步的过程,所以PutIdx和GetIdx移动的瞬时速度不一定相同,但平均速度一致,当PutIdx==GetIdx表明缓冲区是空的,已经无数可取,而当PutIdx-GetIdx=1时,表明缓冲区已满,不允许再存数。
void interrupt TaskQueue::INT1C_Handler(__CPPARGS) // 定时任务发生器
{
int i1;
struct time t;
struct date d;
enable( );
TickCount++; // x86的系统时钟大约55ms中断一次
if( TickCount > = TickSize )
{
GetSystime( &t ); // get current time
if( t.ti_sec != OldTime.ti_sec ) // 作整秒检查
{
ISR_PushCmd( TASK1 ); // 每秒执行一次TASK1
TickSize = 18; // 整秒对齐
TickCount = 0;
OldTime.ti_sec = t.ti_sec;
if( t.ti_min != OldTime.ti_min ) // 作整分检查
{
ISR_PushCmd( TASK2 ); // 每分钟执行一次TASK2
OldTime.ti_min = t.ti_min; // update minute then
if( OldTime.ti_hour != t.ti_hour ) // processing hour data
{
ISR_PushCmd( TASK3 ); // 每小时执行一次TASK3
OldTime.ti_hour = t.ti_hour; // update hour then
}
}
}
}
}
按照上述代码实现的方法,用户很容易实现其他时间间隔的定时任务。
3.程序程序运行测试分析
建议每个任务的每次执行时间控制在100ms,以便系统合理的分配各任务的执行时间,节约系统的数据buffer开销。对大多数应用来说,这一要求很容易得到满足。本应用程序方案首先在NetBox-II(CPU主频24MHz)进行了测试,其任务调度的时间在90us水平,对100ms的任务间隔,系统占用时间小于1%,是完全可以接受的。
对于网络应用,由于存在与对端的交互式操作,所以其整个通讯过程会超过100ms,这时合理的安排是利用等待对端响应的时间来处理系统的其它任务,因此需要在相应的任务中采用状态机的方式来实现,具体的实现会在后续的应用程序方案中介绍。
成都英创信息技术有限公司
地址:成都市高新区高朋大道5号博士创业园
邮编:610041
客服电话:86-28-86180660 85140028 85145208
传真:86-28-85141028
公司网址:
技术支持邮箱:support@emtronix.com
[]