给51 DIY超轻量级多任务操作系统

2020-01-12 17:07发布

前言

想了很久,要不要写这篇文章?最后觉得对操作系统感兴趣的人还是很多,写吧.我不一定能造出玉,但我可以抛出砖.

包括我在内的很多人都对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)
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
98条回答
REI_AYANAMI
1楼-- · 2020-01-19 07:37
谢谢楼主的解答~继续期待楼主的下次讲解~
zjybest
2楼-- · 2020-01-19 09:43
文章不错,值得学习。
      任务栈切换确实比较简单,但是有个问题:就是环境的保存和恢复,在这篇文章里只是讨论如何回避这个问题。
zhangxiaojun
3楼-- · 2020-01-19 10:51
好贴!
346675655
4楼-- · 2020-01-19 14:40
 精彩回答 2  元偷偷看……
rainyss
5楼-- · 2020-01-19 18:14
TO62楼
"任务栈切换确实比较简单,但是有个问题:就是环境的保存和恢复,在这篇文章里只是讨论如何回避这个问题。"


先来一句开场白:天下没有免费的午餐,牺牲少量的自由可以换取巨大的速度提升!!!!

所谓的"环境的保存和恢复",就是上下文.我的核心理念就是尽可能的避过上下文,而不是去保存它们,原因是这样的:

1.对于51这样的CPU来说,对寄存器进行保护会占用大量的存储(每任务+8字节),以及大量的时间(保存/恢复8个寄存器需要额外支出最少16个机器周期).
2.即使保存了寄存器,还得面对内存变量的覆盖问题.而解决内存变量的覆盖与解决寄存器变量覆盖的方法在KEIL中完全相同.

很多人会跳起来说保存寄存器是所有操作系统都是要做的,我只说一句:别太定向思维

可以换个思维方式:

所谓的上下文,不仅包括了堆栈和各寄存器,其实也包括了所有的内存的单元以及各种片上外设,仅将寄存器和堆栈划分到上下文中而其它部分取名为"公共资源"显然只是一厢情愿的定势思维.反过来说,既然可以选择避过那么多公共资源,又何不将寄存器也一起避过呢(除非是无法避过)?

事实上KEIL的编器给我们提供了这种机制,只要我们遵守一些简单的约定,就可以完全地避过寄存器保护的问题.

我手头最快的版本,切换一个任务只需10个机器周期,这是靠避过寄存器保护而换取的,否则,时间马上飚升至100周期.且对内存的巨大占用将使得在128B的片子上由原先的"免强可用"变成"完全不可用".

保护寄存器的版本我早就做过,意义不大,与现有的大量流行版本相比,没有什么优势和特点.
顺便提一句,省内存的版本我也做过,每字节堆栈开销为6机器周期!!!

讲的就是速度
Chenxg
6楼-- · 2020-01-19 23:56
强!!

一周热门 更多>