51简单操作系统OS.doc
上传人:qw****27 上传时间:2024-09-12 格式:DOC 页数:4 大小:30KB 金币:15 举报 版权申诉
预览加载中,请您耐心等待几秒...

51简单操作系统OS.doc

51简单操作系统OS.doc

预览

在线预览结束,喜欢就下载吧,查找使用更方便

15 金币

下载此文档

如果您无法下载资料,请参考说明:

1、部分资料下载需要金币,请确保您的账户上有足够的金币

2、已购买过的文档,再次下载不重复扣费

3、资料包下载后请先用软件解压,在使用对应软件打开

当然,这里要申明一下,这玩意儿其实算不上真正的操作系统,它除了并行多任务并行外根本没有别的功能.但凡事都从简单开始,搞懂了它,就能根据应用需求,将它扩展成一个真正的操作系统.好了,代码来了.将下面的代码直接放到KEIL里编译,在每个task?()函数的"task_switch();"那里打上断点,就可以看到它们的确是"同时"在执行的.#include<reg51.h>#defineMAX_TASKS2//任务槽个数.必须和实际任务数一至#defineMAX_TASK_DEP12//最大栈深.最低不得少于2个,保守值为12.unsignedcharidatatask_stack[MAX_TASKS][MAX_TASK_DEP];//任务堆栈.unsignedchartask_id;//当前活动任务号//任务切换函数(任务调度器)voidtask_switch(){task_sp[task_id]=SP;if(++task_id==MAX_TASKS)task_id=0;SP=task_sp[task_id];}//任务装入函数.将指定的函数(参数1)装入指定(参数2)的任务槽中.如果该槽中原来就有任务,则原任务丢失,但系统本身不会发生错误.voidtask_load(unsignedintfn,unsignedchartid){task_sp[tid]=task_stack[tid]+1;task_stack[tid][0]=(unsignedint)fn&0xff;task_stack[tid][1]=(unsignedint)fn>>8;}//从指定的任务开始运行任务调度.调用该宏后,将永不返回.#defineos_start(tid){task_id=tid,SP=task_sp[tid];return;}/*============================以下为测试代码============================*/voidtask1(){staticunsignedchari;while(1){i++;task_switch();//编译后在这里打上断点}}voidtask2(){staticunsignedcharj;while(1){j+=2;task_switch();//编译后在这里打上断点}}voidmain(){//这里装载了两个任务,因此在定义MAX_TASKS时也必须定义为2task_load(task1,0);//将task1函数装入0号槽task_load(task2,1);//将task2函数装入1号槽os_start(0);}限于篇幅我已经将代码作了简化,并删掉了大部分注释,大家可以直接下载源码包,里面完整的注解,并带KEIL工程文件,断点也打好了,直接按ctrl+f5就行了.现在来看看这个多任务系统的原理:这个多任务系统准确来说,叫作"协同式多任务".所谓"协同式",指的是当一个任务持续运行而不释放资源时,其它任务是没有任何机会和方式获得运行机会,除非该任务主动释放CPU.在本例里,释放CPU是靠task_switch()来完成的.task_switch()函数是一个很特殊的函数,我们可以称它为"任务切换器".要清楚任务是如何切换的,首先要回顾一下堆栈的相关知识.有个很简单的问题,因为它太简单了,所以相信大家都没留意过:我们知道,不论是CALL还是JMP,都是将当前的程序流打断,请问CALL和JMP的区别是什么?你会说:CALL可以RET,JMP不行.没错,但原因是啥呢?为啥CALL过去的就可以用RET跳回来,JMP过去的就不能用RET来跳回呢?很显然,CALL通过某种方法保存了打断前的某些信息,而在返回断点前执行的RET指令,就是用于取回这些信息.不用多说,大家都知道,"某些信息"就是PC指针,而"某种方法"就是压栈.很幸运,在51里,堆栈及堆栈指针都是可被任意修改的,只要你不怕死.那么假如在执行RET前将堆栈修改一下会如何?往下看:当程序执行CALL后,在子程序里将堆栈刚才压入的断点地址清除掉,并将一个函数的地址压入,那么执行完RET后,程序就跳到这个函数去了.事实上,只要我们在RET前将堆栈改掉,就能将程序跳到任务地方去,而不限于CALL里压入的地址.重点来了......首先我们得为每个任务单独开一块内存,这块内存专用于作为对应的任务的堆栈,想将CPU交给哪个任务,只需将栈指针指向谁内存块就行了.接下来我们构造一个这样的函数:当任务调用该函数时,将当前的堆栈指针保存一个变量里,并换上另一个任务的堆栈指针.这就是任务调度器了.OK了,现在我们只要正确的填充好这几个堆栈的原始内容,再调用这个函数,这个任务调度就能运行起来了.那么这几个堆栈里的原始内容是哪里来的呢?这就是"