📄 main.c
字号:
/*
简单的多任务操作系统
其实只有个任务调度切换,把说它是OS有点牵强,但它对于一些简单的开发应用来说,简单也许就是最好的.尽情的扩展它吧.别忘了把你的成果分享给大家.
这是一个最简单的OS,一切以运行效率为重,经测试,在标准51(工作于12M晶振)上,切换一次任务仅20个机器周期,也就是20uS.
而为速度作出的牺牲是,为了给每个任务都分配一个私有堆栈,而占用了较多的内存.作为补偿,多任务更容易安排程序逻辑,从而可以节省一些用于控制的变量.
任务槽越多,占用内存越多,但任务也越好安排,以实际需求合理安排任务数目.一般来说,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()
*/
#include <reg51.h>
#define MAX_TASKS 4//任务槽个数.
/*
任务的栈指针.无任务时保持对应槽为空(0).要强行中止任务时,将对应的任务栈指针置0即可,但无法将自已置空,
因为在task_switch()里的第一句就是保存当前任务的栈指针.想要实现自已清除自已(例如想增加task_exit()功能),在前面一句if(task_sp[task_id])即可
*/
unsigned char idata task_sp[MAX_TASKS];
#define MAX_TASK_DEP 12 //最大栈深.最低不得少于2个,保守值为12.
//预估方法:以2为基数,每增加一层函数调用,加2字节.如果其间可能发生中断,则还要再加上中断需要的栈深.
//减小栈深的方法:1.尽量少嵌套子程序 2.调子程序前关中断.
unsigned char idata task_stack[MAX_TASKS][MAX_TASK_DEP];//任务堆栈.
unsigned char task_id;//当前活动任务号
/*
任务调度器(或者你可以叫它"任务切换函数",只要你不嫌拗口)
这里顺便比较了一下指针与数组的速度.结论是,当任务槽数(注意是任务槽数而不是装载的任务数)高于2时,指针的速度大大快过数组.
随便提一下,很多人都说KEIL C里指针比数组慢,这只能说明他们对KEIL C的指针不了解---必须指定指针的存储模式,才能体现出指针的性能.
参见task_switch()中定义指针t时的注解:
register unsigned char idata *t;//指针永远都是最高效的,除非设计编译器的人是傻子,或者你用错了它-----
//如果去掉指针定义中的idata,则指针变得复杂无比,无任何性能可言.
其实一点也不意外,在访问字节型数组元素时,指针只需要1条指令,数组却需要4条.虽然指针需花费额外开销在赋初值上,但这是一次性的支出,
当循环次数超过2次时,换取回的效率就已经弥补回那些开销.但对于一次性的数组元素访问(即非循环)来说,数组仍是优于指针的.
*/
#if 0
//使用数组搜索任务,代码少
void task_switch(){
//if(task_sp[task_id]) //加上这一句后,任务可通过调用task_exit()宏来退出当前任务
task_sp[task_id] = SP;
while(1){
if(task_id){
task_id--;
}else{
task_id = MAX_TASKS - 1;
}
if(task_sp[task_id] != 0)
break;
}
SP = task_sp[task_id];
}
#else
//使用指针方式搜索任务,性能高.
void task_switch(){
register unsigned char idata *t;//指针永远都是最高效的,除非设计编译器的人是傻子,或者你用错了它-----
//如果去掉指针定义中的idata,则指针变得复杂无比,无任何性能可言.
t = task_sp + task_id;
//if(*t) //加上这一句后,任务可通过调用task_exit()宏来退出当前任务
*t = SP;
while(1){
if(task_id){
task_id--;
t--;
}else{
task_id = MAX_TASKS - 1;
t = task_sp + MAX_TASKS - 1;
}
if(*t)
break;
}
SP = *t;
}
#endif
//将任务装入指定的任务槽中.如果该槽中原来就有任务,则原任务丢失.
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;
}
//停止参数tid所指定的任务槽中的任务
#define task_delete(tid) task_sp[tid] = 0
//中止并退出当前任务.要使用该宏,须在task_switch()函数中启用相应的代码.具体见task_switch()函数中的说明
#define task_exit() task_sp[task_id] = 0,task_switch()
//从指定的任务开始运行任务调度.调用该宏后,将永不返回.
#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;
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;
task_switch();//释放CPU一会儿,让其它进程有机会运行.如果去掉该行,则别的进程永远不会被调用到
}
}
//测试任务:复制内存块.复制完所有字节后释放CPU一次.
void task3(){
//复制全部字节后才释放CPU,控制循环的变量不须考虑覆盖
unsigned char i;//这个变量前不需要加static,因为在它的作用域内并没有释放过CPU
while(1){
i = sizeof(stra);
do{
stra[i-1] = strb[i-1];
}while(--i);
task_switch();//释放CPU一会儿,让其它进程有机会运行.如果去掉该行,则别的进程永远不会被调用到
}
}
void main(){
task_load(task1, 0);//将task1函数装入0号槽
task_load(task2, 1);//将task2函数装入1号槽
task_load(task3, 2);//将task3函数装入2号槽
os_start(0);//启动任务调度,并从0号槽开始运行.参数改为1,则首先运行1号槽.
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -