前言
想了很久,要不要写这篇文章?最后觉得对操作系统感兴趣的人还是很多,写吧.我不一定能造出玉,但我可以抛出砖.
包括我在内的很多人都对51使用操作系统呈悲观态度,因为51的片上资源太少.但对于很多要求不高的系统来说,使用操作系统可以使代码变得更直观,易于维护,所以在51上仍有操作系统的生存机会.
流行的uCos,Tiny51等,其实都不适合在2051这样的片子上用,占资源较多,唯有自已动手,以不变应万变,才能让51也有操作系统可用.这篇贴子的目的,是教会大家如何现场写一个OS,而不是给大家提供一个OS版本.提供的所有代码,也都是示例代码,所以不要因为它没什么功能就说LAJI之类的话.如果把功能写全了,一来估计你也不想看了,二来也失去灵活性没有价值了.
下面的贴一个示例出来,可以清楚的看到,OS本身只有不到10行源代码,编译后的目标代码60字节,任务切换消耗为20个机器周期.相比之下,KEIL内嵌的TINY51目标代码为800字节,切换消耗100~700周期.唯一不足之处是,每个任务要占用掉十几字节的堆栈,所以任务数不能太多,用在128B内存的51里有点难度,但对于52来说问题不大.这套代码在36M主频的STC12C4052上实测,切换任务仅需2uS.
#include <reg51.h>
#define MAX_TASKS 2 //任务槽个数.必须和实际任务数一至
#define MAX_TASK_DEP 12 //最大栈深.最低不得少于2个,保守值为12.
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];
}
//任务装入函数.将指定的函数(参数1)装入指定(参数2)的任务槽中.如果该槽中原来就有任务,则原任务丢失,但系统本身不会发生错误.
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;}
/*============================以下为测试代码============================*/
void task1(){
static unsigned char i;
while(1){
i++;
task_switch();//编译后在这里打上断点
}
}
void task2(){
static unsigned char j;
while(1){
j+=2;
task_switch();//编译后在这里打上断点
}
}
void main(){
//这里装载了两个任务,因此在定义MAX_TASKS时也必须定义为2
task_load(task1, 0);//将task1函数装入0号槽
task_load(task2, 1);//将task2函数装入1号槽
os_start(0);
}
这样一个简单的多任务系统虽然不能称得上真正的操作系统,但只要你了解了它的原理,就能轻易地将它扩展得非常强大,想知道要如何做吗?
所附文件下载:
从单任务到多任务并行系统的演变
ourdev_378093.rar(文件大小:115K) (原文件名:演变.rar)
一个最简单的多任务并行系统
ourdev_378094.rar(文件大小:19K) (原文件名:mtask.rar)
任务栈切换确实比较简单,但是有个问题:就是环境的保存和恢复,在这篇文章里只是讨论如何回避这个问题。
"任务栈切换确实比较简单,但是有个问题:就是环境的保存和恢复,在这篇文章里只是讨论如何回避这个问题。"
先来一句开场白:天下没有免费的午餐,牺牲少量的自由可以换取巨大的速度提升!!!!
所谓的"环境的保存和恢复",就是上下文.我的核心理念就是尽可能的避过上下文,而不是去保存它们,原因是这样的:
1.对于51这样的CPU来说,对寄存器进行保护会占用大量的存储(每任务+8字节),以及大量的时间(保存/恢复8个寄存器需要额外支出最少16个机器周期).
2.即使保存了寄存器,还得面对内存变量的覆盖问题.而解决内存变量的覆盖与解决寄存器变量覆盖的方法在KEIL中完全相同.
很多人会跳起来说保存寄存器是所有操作系统都是要做的,我只说一句:别太定向思维
可以换个思维方式:
所谓的上下文,不仅包括了堆栈和各寄存器,其实也包括了所有的内存的单元以及各种片上外设,仅将寄存器和堆栈划分到上下文中而其它部分取名为"公共资源"显然只是一厢情愿的定势思维.反过来说,既然可以选择避过那么多公共资源,又何不将寄存器也一起避过呢(除非是无法避过)?
事实上KEIL的编器给我们提供了这种机制,只要我们遵守一些简单的约定,就可以完全地避过寄存器保护的问题.
我手头最快的版本,切换一个任务只需10个机器周期,这是靠避过寄存器保护而换取的,否则,时间马上飚升至100周期.且对内存的巨大占用将使得在128B的片子上由原先的"免强可用"变成"完全不可用".
保护寄存器的版本我早就做过,意义不大,与现有的大量流行版本相比,没有什么优势和特点.
顺便提一句,省内存的版本我也做过,每字节堆栈开销为6机器周期!!!
讲的就是速度
一周热门 更多>