|
|
- /*
- 简单的多任务操作系统
- 其实只有个任务调度切换,把说它是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_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)
- {
- //25ms
- if (i < 250)
- {
- i++;
- }
- if (i >= 250)
- {
- LED1 = ~LED1;
- i = 0;
- }
- task_switch();
- }
- }
-
- //经过仿真计算得j=10,即等待1ms
- void func2()
- {
- static unsigned int data j;
- j = 0;
-
- while (1)
- {
- //65ms
- if (j < 650)
- {
- j++;
- }
- if (j >= 650)
- {
- LED2 = ~LED2;
- j = 0;
- }
- task_switch();
- }
- }
-
- /*
- keyscan task
- void task3()
- {
- while(1)
- {
- i = 5;
- do
- {
- sigl = !sigl;
- }while(--i);
- 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(),也就是说,该语句行之后的所有语句都不被执行到.
- }
- ---------------------
- 作者:liming0931
- 来源:CSDN
- 原文:https://blog.csdn.net/liming0931/article/details/81603619
- 版权声明:本文为博主原创文章,转载请附上博文链接!
复制代码
|
|