51单片机最简单的多任务操作系统

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

/*

51单片机最简单的多任务操作系统

其实只有个任务调度切换,把说它是OS有点牵强,但它对于一些简单的开发应用来说,简单也许就是最好的.尽情的扩展它吧.别忘了把你的成果分享给大家.

这是一个最简单的OS,一切以运行效率为重,经测试,切换一次任务仅个机器周期,也就是在标准(工作于M晶振)上uS.

而为速度作出的牺牲是,为了给每个任务都分配一个私有堆栈,而占用了较多的内存.作为补偿,多任务更容易安排程序逻辑,从而可以节省一些用于控制的变量.

任务槽越多,占用内存越多,但任务也越好安排,以实际需求合理安排任务数目.一般来说,4个已足够.况且可以拿一个槽出来作为活动槽,换入换入一些临时任务.

task_load(函数名,任务槽号)

装载任务

os_start(任务槽号)

启动任务表.参数必须指向一个装载了的任务,否则系统会崩溃.

task_switch()

切换到其它任务

.编写任务函数注意事项:

KEIL C编译器是假定用户使用单任务环境,所以在变量的使用上都未对多任务进行处理,编写任务时应注意变量覆盖和代码重入问题.

1.覆盖:编译器为了节省内存,会给两个没用调用关系的函数分配同一内存地址作为变量空间.这在单任务下是很合理的,但对于多任务来说,两个进程会互相干扰对方.

解决的方法是:凡作用域内会跨越task_switch()的变量,都使用static前辍,保证其地址空间分配时的唯一性.

2.重入:重入并不是多任务下独有的问题,在单任务时,函数递归同样会导致重入,即,一个函数的不同实例(或者叫作"复本")之间的变量覆盖问题.

解决的方法是:使用reentrant函数后辍(例如:void function1() reentrant{...}).当然,根本的办法还是避免重入,因为重入会带来巨大的目标代码量,并极大降低运行效率.

3.额外提醒一句,在本例中,任务函数必须为一个死循环.退出函数会导致系统崩溃.

.任务函数如果是用汇编写成或内嵌汇编,切换任务时应该注意什么问题?

由于KEIL C编译器在处理函数调用时的约定规则为"子函数有可能修改任务寄存器",因此编译器在调用前已释放所有寄存器,子函数无需考虑保护任何寄存器.

这对于写惯汇编的人来说有点不习惯: 汇编习惯于在子程序中保护寄存器.

请注意一条原则:凡是需要跨越task_switch()的寄存器,全部需要保护(例如入栈).根本解决办法还是,不要让寄存器跨越任务切换函数task_switch()

事实上这里要补充一下,正如前所说,由于编译器存在变量地址覆盖优化,因此凡是非静态变量都不得跨越

task_switch().

任务函数的书写:

void 函数名(void){//任务函数必须定义为无参数型

while(1){//任务函数不得返回,必须为死循环

//....这里写任务处理代码

task_switch();//每执行一段时间任务,就释放CPU一下,让别的任务有机会运行.

}

}

任务装载:

task_load(函数名,任务槽号)

装载函数的动作可发生在任意时候,但通常是在main()中.要注意的是,在本例中由于没考虑任务换出,

所以在执行os_start()前必须将所有任务槽装满.之后可以随意更换任务槽中的任务.

启动任务调度器:

os_start(任务槽号)

调用该宏后,将从参数指定的任务槽开始执行任务调度.本例为每切换一次任务需额外开销个机器周期,用于迁移堆栈.

*/

#include

/*============================以下为任务管理器代码============================*/

#define MAX_TASKS 3//任务槽个数.在本例中并未考虑任务换入换出,所以实际运行的任务有多少个,就定义多少个任务槽,不可多定义或少定义

//任务的栈指针

unsigned char idata task_sp[MAX_TASKS];

#define MAX_TASK_DEP 12 //最大栈深.最低不得少于个,保守值为.

//预估方法:以为基数,每增加一层函数调用,加字节.如果其间可能发生中断,则还要再加上中断需要的栈深.

//减小栈深的方法:1.尽量少嵌套子程序2.调子程序前关中断.

unsigned char idata task_stack[MAX_TASKS][MAX_TASK_DEP];//任务堆栈.

unsigned char task_id;//当前活动任务号

//任务切换函数(任务调度器)

void task_switch(){

task_sp[task_id] = SP;

if(++task_id == MAX_TASKS)

task_id = 0;

SP = task_sp[task_id];

}

//任务装入函数.将指定的函数(参数)装入指定(参数)的任务槽中.如果该槽中原来就有任务,则原任务丢失,但

系统本身不会发生错误.

void task_load(unsigned int fn, unsigned char tid){

task_sp[tid] = task_stack[tid] + 1;

task_stack[tid][0] = (unsigned int)fn & 0xff;

task_stack[tid][1] = (unsigned int)fn >> 8;

}

//从指定的任务开始运行任务调度.调用该宏后,将永不返回.

#define os_start(tid) {task_id = tid,SP = task_sp[tid];return;}

/*============================以下为测试代码============================*/

unsigned char stra[3], strb[3];//用于内存块复制测试的数组.

//测试任务:复制内存块.每复制一个字节释放CPU一次

void task1(){

//每复制一个字节释放CPU一次,控制循环的变量必须考虑覆盖

static unsigned char i;//如果将这个变量前的static去掉,会发生什么事?

i = 0;

while(1){//任务必须为死循环,不得退出函数,否则系统会崩溃

stra[i] = strb[i];

if(++i == sizeof(stra))

i = 0;

//变量i在这里跨越了task_switch(),因此它必须定义为静态(static),否则它将会被其它进程修改,因为在另一个进程里也会用到该变量所占用的地址.

task_switch();//释放CPU一会儿,让其它进程有机会运行.如果去掉该行,则别的进程永远不会被调用到

}

}

//测试任务:复制内存块.每复制一个字节释放CPU一次.

void task2(){

//每复制一个字节释放CPU一次,控制循环的变量必须考虑覆盖

static unsigned char i;//如果将这个变量前的static去掉,将会发生覆盖问题.task1()和task2()会被编译器分配到同一个内存地址上,当两个任务同时运行时,i的值就会被两个任务改来改去

i = 0;

while(1){//任务必须为死循环,不得退出函数,否则系统会崩溃

stra[i] = strb[i];

if(++i == sizeof(stra))

i = 0;

//变量i在这里跨越了task_switch(),因此它必须定义为静态(static),否则它将会被其它进程修改,因为在另一个进程里也会用到该变量所占用的地址.

task_switch();//释放CPU一会儿,让其它进程有机会运行.如果去掉该行,则别的进程永远不会被调

相关文档
最新文档