找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 4921|回复: 5
收起左侧

简单的多任务操作系统程序源码

[复制链接]
ID:20672 发表于 2018-7-22 09:38 | 显示全部楼层 |阅读模式
这是一个可以运行的多任务程序,参考了网上的教程,十分感谢,自己添加了两个led的任务,可以运行的,程序也不大。
/*

简单的多任务操作系统

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

这是一个最简单的OS,一切以运行效率为重,经测试,切换一次任务仅20个机器周期,
也就是在标准51(工作于12M晶振)上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()
事实上这里要补充一下,正如前所说,由于编译器存在变量地址覆盖优化,
因此凡是非静态变量都不得跨越task_switch().


任务函数的书写:
void 函数名(void)
{        //任务函数必须定义为无参数型
        while(1)
        {
                //任务函数不得返回,必须为死循环
                //....这里写任务处理代码

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


任务装载:
task_load(函数名,任务槽号)

装载函数的动作可发生在任意时候,但通常是在main()中.要注意的是,
在本例中由于没考虑任务换出,
所以在执行os_start()前必须将所有任务槽装满.之后可以随意更换任务槽中的任务.

启动任务调度器:
os_start(任务槽号)

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


#include <reg52.h>

sbit LED1 = P2^0;
sbit LED2 = P2^1;
void func1();
void func2();

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

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

//任务的栈指针
unsigned char idata task_sp[MAX_TASKS];

//最大栈深.最低不得少于2个,保守值为12.
//预估方法:以2为基数,每增加一层函数调用,加2字节.
//如果其间可能发生中断,则还要再加上中断需要的栈深.
//减小栈深的方法:1.尽量少嵌套子程序 2.调子程序前关中断.
#define MAX_TASK_DEP 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)的任务槽中.
//如果该槽中原来就有任务,则原任务丢失,但系统本身不会发生错误.
//将各任务的函数地址的低字节和高字节分别入在
//task_stack[任务号][0]和task_stack[任务号][1]中
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一会儿,让其它进程有机会运行.如果去掉该行,则别的进程永远不会被调用到
        }
}

//测试任务:复制内存块.复制完所有字节后释放CPU一次.
void task3()
{
        //复制全部字节后才释放CPU,控制循环的变量不须考虑覆盖
        unsigned char i;//这个变量前不需要加static,
        //因为在它的作用域内并没有释放过CPU

        while(1) //任务必须为死循环,不得退出函数,否则系统会崩溃
        {
                i = sizeof(stra);
                do
                {
                        stra[i-1] = strb[i-1];
                }
                while(--i);

                //变量i在这里已完成它的使命,所以无需定义为静态.
                //你甚至可以定义为寄存器型(regiter)
                task_switch();//释放CPU一会儿,让其它进程有机会运行.如果去掉该行,
                //则别的进程永远不会被调用到
        }
}



/*
my first task
*/

void func1()
{
        static unsigned char data i;
        i = 0;

        while(1)
        {
                if(i<250)
                {
                        i++;
                }
                if(i>=250)
                {
                        LED1 = ~LED1;
                        i = 0;                        
                }
                task_switch();
        }
}
void func2()
{
        static unsigned int data j;
        j = 0;

        while(1)
        {
                if(j<654)
                {
                        j++;
                }
                if(j>=654)
                {
                        LED2 = ~LED2;
                        j = 0;                                
                }
                task_switch();
        }
}


/*
void func1()
{
    register char data i;
    while(1)
        {
        i = 5;
        do
                {
                sigl = !sigl;
        }while(--i);
        task_switch();
    }
}
void func2()
{
    register char data i;
    while(1)
        {
        i = 5;
        do
                {
                sigl = !sigl;
        }while(--i);
        task_switch();
    }
}
*/

void main()
{
        //在这个示例里并没有考虑任务的换入换出,所以任务槽必须全部用完,否则系统会崩溃.
        //这里装载了三个任务,因此在定义MAX_TASKS时也必须定义为5
        task_load(task1, 0);//将task1函数装入0号槽
        task_load(task2, 1);//将task2函数装入1号槽
        task_load(task3, 2);//将task3函数装入2号槽
        task_load(func1, 3);//将task3函数装入3号槽
        task_load(func2, 4);//将task3函数装入4号槽

        os_start(0);//启动任务调度,并从0号槽开始运行.参数改为1,则首先运行1号槽.
        //调用该宏后,程序流将永不再返回main(),也就是说,该语句行之后的所有语句都不被执行到.
}

评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

回复

使用道具 举报

ID:394678 发表于 2018-9-9 18:04 | 显示全部楼层
好,我现在有个项目这好用到这个
回复

使用道具 举报

ID:388870 发表于 2018-9-11 08:58 | 显示全部楼层
谢谢分享
回复

使用道具 举报

ID:66287 发表于 2018-9-14 09:09 | 显示全部楼层
刚编译运行了一下,很好
回复

使用道具 举报

ID:364805 发表于 2019-6-22 09:59 | 显示全部楼层

谢谢分享
回复

使用道具 举报

ID:382454 发表于 2019-6-22 11:55 | 显示全部楼层
菜鸟看到这个,太深奥了。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表