之前看过一篇卢晓铭写的简易RTOS设计,自己也实践了一下,感觉多任务运行起来毫无压力,在此做份笔记以备他日之需也为他人提供一份参考
要想完成一个RTOS,我们核心任务是需要编写任务调度。 所以,我们需要知道,任务到底什么地方会被调度。 1. 我们开始OSStart();时,肯定需要调度一次任务。这样才能进入第一个最高优先级就绪任务中。 2. 在任务中的OSTimeDly();延时函数中,我们需要进行任务调度。当前任务延时了,肯定就要换一个别的任务来运行呀。 3. 在中断退出时,需要进行任务调度(该处主要指定时器中断),可以理解为每个时钟周期都在进行任务最高优先级别的检验。
任务状态的标志,我想我们可以用1bit来代表,0:代表任务挂起或不存在,1:代表任务就绪。 U32 OSRdyTbl; 这是一个32bit的任务就绪表,每一位代表任务的状态. /*在就绪表中登记任务*/ __inline void OSSetPrioRdy(u8 prio) //__inline是内联函数,用在该处是为了提高效率。关于内联函数的详情和使用,可以百度。 { OSRdyTbl|= 1 << prio; }
/*在就绪表中删除任务*/ __inline void OSDelPrioRdy(u8 prio) { OSRdyTbl&= ~(1<<prio); }
在每个任务调度前,肯定需要知道当前最高优先级的就绪任务是什么,所以我们需要一个查找函数 /*在就绪表中查找更高级的就绪任务*/ __inline void OSGetHighRdy(void) { u8OSNextTaskPrio = 0; /*任务优先级*/
for(OSNextTaskPrio = 0; (OSNextTaskPrio < OS_TASKS) &&(!(OSRdyTbl&(0x01<<OSNextTaskPrio))); OSNextTaskPrio++ ); //注意for最后有个分号 OSPrioHighRdy= OSNextTaskPrio; //获得最高优先级,OSPrioHighRdy是一个全局变量 }
根据分析,我们知道我们的简易RTOS有3个地方会出现任务调度 首先是任务刚开始时. 我们先定义几个全局变量: 1. OSRunning指示OS是否开始运行 2. OSPrioCur指示当前任务优先级 3. OSPrioHighRdy指示当前已就绪的最高优先级任务,由OSGetHighRdy函数更新 void OSStart(void) { if(OSRunning== 0) //OSRuning是一个全局变量 { OSRunning= 1;
//先不忙讲任务创建 OSTaskCreate(IdleTask, (void *)0,(u32 *)&IDELTASK_STK[31], IdelTask_Prio); //创建空闲任
OSGetHighRdy(); /*获得最高级的就绪任务*/ OSPrioCur= OSPrioHighRdy; /*获得最高优先级就绪任务ID*/ p_OSTCBCur= &TCB[OSPrioCur]; p_OSTCBHighRdy= &TCB[OSPrioHighRdy]; OSStartHighRdy(); //在汇编语句中 } } 其次出现任务调度的地方是延时函数OSTimeDly() 在这个函数前,要先介绍一个结构体TCB,这是任务控制怪(Task Control Block) 每一个任务都有一个任务控制块,这个控制块记录着任务的重要信息,由于该处是简易OS设计,所以仅仅有两种 typedef struct TaskCtrBlockHead /*任务控制块数据结构*/ { u32OSTCBStkPtr; /*保存任务栈顶地址*/ u32OSTCBDly; /*任务延时时钟*/ }TaskCtrBlock; #define OS_TASKS 32 //最多任务数 TaskCtrBlock TCB[OS_TASKS]; //定义任务TCB,由于最多有32个任务,所以该处定义32个TCB
void OSTimeDly(u32 ticks) { if(ticks> 0) { OS_ENTER_CRITICAL(); //进入临界区 OSDelPrioRdy(OSPrioCur); //将任务挂起 TCB[OSPrioCur].OSTCBDly= ticks; //设置TCB中任务延时节拍数 OS_EXIT_CRITICAL(); //退出临界区 OSSched(); //任务调度 } } /*任务切换*/ void OSSched(void) { OSGetHighRdy(); //找出任务就绪表中优先级最高的任务 if(OSPrioHighRdy!=OSPrioCur) //如果不是当前运行任务,进行任务调度 { p_OSTCBCur= &TCB[OSPrioCur]; //以便在汇编中引用,可以理解为用于将现在的任务环境保存在该指针指向的堆栈中 //直接把当前任务的堆栈指针保存到当前任务的TCB(TCB的OSTCBStkPtr) p_OSTCBHighRdy= &TCB[OSPrioHighRdy]; //以便在汇编中引用,可以理解为用于将该指针中的数据释放出来,恢复指定任务环境 //直接把最高优先级就绪任务的TCB(TCB的OSTCBStkPtr)设为当前任务的堆栈指针 OSPrioCur= OSPrioHighRdy; //更新OSPrio OSCtxSw(); //调度任务,在汇编中引用 } }
最后是在时钟中断中出现 由于每次时钟中断我们都需要解决任务时钟延时问题,所以我们需要一个函数 /*定时器中断对任务延时处理函数*/ void TicksInterrupt(void) { static u8i;
OSTime++; for(i=0;i<OS_TASKS;i++) { if(TCB.OSTCBDly) { TCB.OSTCBDly--; if(TCB.OSTCBDly==0) //延时时钟到达 { OSSetPrioRdy(i); //任务重新就绪 } } } }
//系统时钟中断服务函数 void SysTick_Handler(void) { OS_ENTER_CRITICAL(); //进入临界区 OSIntNesting++; //任务前套数 OS_EXIT_CRITICAL(); //退出临界区 TicksInterrupt(); // OSIntExit(); //在中断中处理任务调度 } void OSIntExit(void) { OS_ENTER_CRITICAL(); //进入临界区
if(OSIntNesting> 0) OSIntNesting--; if(OSIntNesting== 0) //没有中断嵌套时,才可以进行任务调度 { OSGetHighRdy(); /*找出任务优先级最高的就绪任务*/ if(OSPrioHighRdy!=OSPrioCur) /*当前任务并非优先级最高的就绪任务*/ { p_OSTCBCur= &TCB[OSPrioCur]; p_OSTCBHighRdy= &TCB[OSPrioHighRdy]; OSPrioCur= OSPrioHighRdy; OSIntCtxSw(); /*中断级任务调度,注意这里和OSCtxSw不一样,但是作用是一样的*/ } } OS_EXIT_CRITICAL(); //退出临界区 }
现在任务调度已经写完了,那么应该要创建任务了吧,这里使用创建任务函数 /*任务创建*/ void OSTaskCreate(void (*Task)(void *parg), void *parg,u32 *p_Stack, u8 TaskID) { if(TaskID<= OS_TASKS) { *(p_Stack) = (u32)0x01000000L; /* xPSR */ *(--p_Stack) = (u32)Task; /* Entry Point of the task 任务入口地址 */ *(--p_Stack) = (u32)0xFFFFFFFEL; /* R14 (LR) (init value will */
*(--p_Stack) = (u32)0x12121212L; /* R12 */ *(--p_Stack) = (u32)0x03030303L; /* R3 */ *(--p_Stack) = (u32)0x02020202L; /* R2 */ *(--p_Stack) = (u32)0x01010101L; /* R1 */ *(--p_Stack) = (u32)parg; /* R0 : argument 输入参数 */
*(--p_Stack) = (u32)0x11111111L; /* R11 */ *(--p_Stack) = (u32)0x10101010L; /* R10 */ *(--p_Stack) = (u32)0x09090909L; /* R9 */ *(--p_Stack) = (u32)0x08080808L; /* R8 */ *(--p_Stack) = (u32)0x07070707L; /* R7 */ *(--p_Stack) = (u32)0x06060606L; /* R6 */ *(--p_Stack) = (u32)0x05050505L; /* R5 */ *(--p_Stack) = (u32)0x04040404L; /* R4 */
TCB[TaskID].OSTCBStkPtr= (u32)p_Stack; /*保存堆栈地址*/ TCB[TaskID].OSTCBDly= 0; /*初始化任务延时*/ OSSetPrioRdy(TaskID); /*在任务就绪表中登记*/ } } 这里主要是入栈寄存器地址
|