前言数组
看了好久的操做系统原理,ucos源码也看了大半,可是感受老是懵懵懂懂,用句流行的网络用语就是始终上不了车,后来在网上被人推荐了一篇文章《创建一个属于本身的操做系统》,这篇文章真的很是好,也附有源码,但不知道是否是我找的文章有差错仍是啥,我根据文章提供的源码贴代码,根本没法编译,而后开始读代码修改代码最后成功编译可是在硬件平台运行根本不行。后来又断断续续看ucos源码,反正各类什么数据结构啊的通讯什么的让人头痛,后来大学的单片机原理完课,学校安排课设,我选了时钟定时器(有点像闹钟),这种开环的裸机开发没什么难度,闲着也是闲着因而重新捡起几个月前没有完成的os,此次从新开坑,代码彻底本身敲,从基本功能开始一步步实现os,像《创建一个属于本身的操做系统》介绍的同样。忙活了两天也终于成功了,而且成功地将时钟定时器移植到本身的os上跑了,说实话在os跑比裸机的先后台的效果好不少(os上跑按按键与现实感受是同步进行的,先后台的效果在按按键的时候数码显示是会黑屏的),可是51的硬件资源太少,只有128个字节的ram,因此这次设计没有统一的任务通讯接口,只能实现基本的优先级,延时服务或者轮询服务。在本身成功地创建一个属于本身os后在看ucos的源码更加顺畅,之前一直搞不懂的任务通讯也能明白一二(不过也得谢谢任哲写的那本《嵌入式操做系统基础ucOS-Ⅱ和Linux(第2版)》),废话很少说,正文以下:网络
ps:第一次写这种博客,写的很差望谅解,因为《创建一个属于本身的操做系统》自己就写的很详细,因此我只写出os创建的核心部分。数据结构
正文函数
1,任务人口地址:在os中,是在不直接用程序名(参数)这种方式调用任务。那怎样呢?这部分《创建一个属于本身的操做系统》讲的很是详细,你们自行搜阅。spa
2,任务调度:学过单片机原理的都知道,cpu中有sp与pc两个特殊的寄存器,sp是堆栈指针,在51中它能够指向数据区的任意单元,PC是程序计数器,它始终保存下一条程序指令的地址。51C语言是能够直接操控sp的,可是pc不行,因此要想办法间接操控pc,对的,就是经过压栈和弹栈实现,在程序执行发生断点时(调动子程序或中断),cpu会自动将pc的值进行压栈,返回断点时会自动将栈顶的值弹回pc,这就是关键,若是在弹回前,咱们修改sp,不就能够间接操控pc了吗!这样就能够将cpu执行其余任务了;操作系统
3,人工堆栈:操做系统原理中有一点很是重要,就是上下文切换,因此每一个任务必须有属于本身的堆栈,称为人工堆栈。人工堆栈的创建很是讲究,不能短也不能太长,短了会是溢出会可能修改其余任务的人工堆栈,产生调度紊乱。太长会浪费空间,尤为是像51这种硬件资源本就少的单片机。堆栈的空间的预留是经过数组来划分的。在创建任务时,要对堆栈初始化(这也很关键),将任务入口地址压到最底部(不一样的单片机状况不一样,这里以51为例,后面的也是),而后sp指向正确的堆栈位置(不一样的单片机状况不一样,要保存的寄存器个数不一样),我的在设计中发现,为了避免让sp越界,最好将堆栈最底部单元预留出来,避免浪费能够用来保存任务信息,好比堆栈使用状况。设计
void Task_Creak(void (*pfun)(void),INT8U *pStack,INT8U Task_ID) { INT8U *pSt; OS_ENTER_CRITICAL(); pSt=pStack; *(++pSt)=(INT16U)pfun; *(++pSt)=(INT16U)pfun>>8; os_tcb[Task_ID].OSTCBSP=(INT8U)pSt+13; OS_Task_List|=OSMapTbl[Task_ID]; os_tcb[Task_ID].OSTCBDly=0; OS_EXIT_CRITICAL(); }
4,任务控制块:和人工堆栈同样每一个任务也有属于本身的任务控制块,根据系统需求成员定义不一样,对于自由延时服务的os,只须要一个保存任务SP的成员变量和保存延时时间的成员变量。指针
typedef struct{ INT8U OSTCBSP; INT8U OSTCBDly; }OS_TCB;
5,系统时间:也叫时钟节拍,是系统的心脏,有硬件产生,51能够用定时器产生毫秒级中断。code
void StartTicker(void) { TMOD=0x01; TH0=0x0d8; TL0=0x0f0; ET0=1; TR0=1; }
6,系统延时函数接口:用于任务延时,在延时的时候让cpu去执行其余任务,提升cpu的效率(经过实践,我我的以为这也是软实时实现的原因),在这个函数中要完成sp保存,将任务踢出就绪表,而后调度。blog
void OSTimeDly(INT8U ticks) { OS_ENTER_CRITICAL(); os_tcb[CurID].OSTCBDly=ticks; OS_Task_List&=~OSMapTbl[CurID]; OS_EXIT_CRITICAL(); OS_TASK_SW(); }
7,调度函数:有两种,一种是普通的调度,用于延时调度,因此要插入汇编语言先后分别将现场保护和现场恢复,还有就是完成获取最高任务和sp获取。一种是中断级别的调度,
用于中断服务程序,因为C语言编译成汇编时编译器会自动现场保护,因此只要在调度函数中只要现场恢复,还有就是在完成获取最高任务和sp获取前,要完成sp保存,将任务踢出就绪表。
void OS_TASK_SW(void) { INT8U i; EA=0; #pragma asm PUSH ACC PUSH B PUSH DPH PUSH DPL PUSH PSW MOV PSW,#00H PUSH AR0 PUSH AR1 PUSH AR2 PUSH AR3 PUSH AR4 PUSH AR5 PUSH AR6 PUSH AR7 #pragma endasm os_tcb[CurID].OSTCBSP=SP; for(i=0;i<MAX_TASK;i++) { if(OS_Task_List&OSMapTbl[i]) { break; } } CurID=i; SP=os_tcb[CurID].OSTCBSP; #pragma asm POP AR7 POP AR6 POP AR5 POP AR4 POP AR3 POP AR2 POP AR1 POP AR0 POP PSW POP DPL POP DPH POP B POP ACC #pragma endasm EA=1; #pragma asm RETI; #pragma endasm }
void TickInterrupt(void) { INT8U i; // SP-=2; for(i=0;i<MAX_TASK;i++) { if(os_tcb[i].OSTCBDly>0) { os_tcb[i].OSTCBDly--; if(os_tcb[i].OSTCBDly==0) OS_Task_List|=OSMapTbl[i]; } } SP-=2; os_tcb[CurID].OSTCBSP=SP; //OS_Task_List&=~OSMap[CurID]; for(i=0;i<MAX_TASK;i++) { if(OS_Task_List&OSMapTbl[i]) { break; } } CurID=i; SP=os_tcb[CurID].OSTCBSP; #pragma asm POP AR7 POP AR6 POP AR5 POP AR4 POP AR3 POP AR2 POP AR1 POP AR0 POP PSW POP DPL POP DPH POP B POP ACC #pragma endasm EA=1; #pragma asm RETI #pragma endasm }
8,sp控制:在调度的过程当中,必须保证将任务断点的入口地址保存在堆栈最底部(预留单元上面),再次在任务调度过程当中不免会调用其余函数再次压栈,并且可能不在会这个断点,
因此在被调用的这个程序中将sp下调两位。因此在os设计中sp的控制必须十分当心,不能任务调度必定会紊乱。
9.任务函数:os中必须存在一个不能主动申请调度的任务,称为系统任务,为了cpu在没有任何任务的时候有事可作,我的在实践中发现没有这种任务os会出错。
还有就是要把握任务集的可调度性(其中受任务优先级设计影响)。
10:就绪表:就绪表的设计要是相当重要,《操做系统原理》自阅。
最后:本人菜鸟一枚,有错见谅,谢谢观阅!
ps:之后有机会会贴上工程代码及效果图。