之前看过一篇卢晓铭写的简易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[i].OSTCBDly)
{
TCB[i].OSTCBDly--;
if(TCB[i].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); /*在任务就绪表中登记*/
}
}
这里主要是入栈寄存器地址
为了保证系统的正常运行,我们需要一个空闲任务,空闲任务可以什么事情都不做,也可以随便做点什么简单的事。
/*系统空闲任务*/
void IdleTask(void *pdata)
{
u32IdleCount = 0;
while(1)
{
IdleCount++;
}
}
既然如此,那么系统开始前应该申请一个空闲任务,所以OSStart()函数改为
void OSStart(void)
{
if(OSRunning== 0)
{
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();
}
}
以上就是任务调度器的核心.
至于汇编代码,可以直接参考ucosii在STM32上的汇编代码。
文件RTOS.c
/*********************** (C) COPYRIGHT 2013 Libraworks *************************
* File Name : RTOS.c
* Author : 卢晓铭
* Version : V1.0
* Date : 01/26/2013
* Description : LXM-RTOS 任务管理
*******************************************************************************/
#include"RTOS.h"
TaskCtrBlock TCB[OS_TASKS - 1]; /*任务控制块定义*/
TaskCtrBlock *p_OSTCBCur; /*指向当前任务控制块的指针*/
TaskCtrBlock *p_OSTCBHighRdy; /*指向最高优先级就绪任务控制块的指针*/
u8 OSPrioCur; /*当前执行任务*/
u8 OSPrioHighRdy; /*最高优先级*/
u8 OSRunning; /*多任务运行标志0:为运行,1:运行*/
u32 OSInterruptSum; /*进入中断次数*/
u32 OSTime; /*系统时间(进入时钟中断次数)*/
u32 OSRdyTbl; /*任务就绪表,0表示挂起,1表示就绪*/
u32 OSIntNesting; /*任务嵌套数*/
/*在就绪表中登记任务*/
void OSSetPrioRdy(u8 prio)
{
OSRdyTbl |= 1 << prio;
}
/*在就绪表中删除任务*/
void OSDelPrioRdy(u8 prio)
{
OSRdyTbl &= ~(1<<prio);
}
/*在就绪表中查找更高级的就绪任务*/
void OSGetHighRdy(void)
{
u8 OSNextTaskPrio = 0; /*任务优先级*/
for (OSNextTaskPrio = 0; (OSNextTaskPrio < OS_TASKS) && (!(OSRdyTbl&(0x01<<OSNextTaskPrio))); OSNextTaskPrio++ );
OSPrioHighRdy = OSNextTaskPrio;
}
/*设置任务延时时间*/
void OSTimeDly(u32 ticks)
{
if(ticks > 0)
{
OS_ENTER_CRITICAL(); //进入临界区
OSDelPrioRdy(OSPrioCur); //将任务挂起
TCB[OSPrioCur].OSTCBDly = ticks; //设置TCB中任务延时节拍数
OS_EXIT_CRITICAL(); //退出临界区
OSSched();
}
}
/*定时器中断对任务延时处理函数*/
void TicksInterrupt(void)
{
static u8 i;
OSTime++;
for(i=0;i<OS_TASKS;i++)
{
if(TCB[i].OSTCBDly)
{
TCB[i].OSTCBDly--;
if(TCB[i].OSTCBDly==0) //延时时钟到达
{
OSSetPrioRdy(i); //任务重新就绪
}
}
}
}
/*任务切换*/
void OSSched(void)
{
OSGetHighRdy(); //找出任务就绪表中优先级最高的任务
if(OSPrioHighRdy!=OSPrioCur) //如果不是当前运行任务,进行任务调度
{
p_OSTCBCur = &TCB[OSPrioCur]; //汇编中引用
p_OSTCBHighRdy = &TCB[OSPrioHighRdy]; //汇编中引用
OSPrioCur = OSPrioHighRdy; //更新OSPrio
OSCtxSw(); //调度任务
}
}
/*任务创建*/
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); /*在任务就绪表中登记*/
}
}
void OSTaskSuspend(u8 prio)
{
OS_ENTER_CRITICAL(); /*进入临界区*/
TCB[prio].OSTCBDly = 0;
OSDelPrioRdy(prio); /*挂起任务*/
OS_EXIT_CRITICAL(); /*退出临界区*/
if(OSPrioCur == prio) /*挂起的任务为当前运行的任务*/
{
OSSched(); /*重新调度*/
}
}
void OSTaskResume(u8 prio)
{
OS_ENTER_CRITICAL();
TCB[prio].OSTCBDly = 0; /*设置任务延时时间为0*/
OSSetPrioRdy(prio); /*就绪任务*/
OS_EXIT_CRITICAL();
if(OSPrioCur > prio) /*当前任务优先级小于恢复的任务优先级*/
{
OSSched();
}
}
u32 IDELTASK_STK[32];
void OSStart(void)
{
if(OSRunning == 0)
{
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();
}
}
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(); /*中断级任务调度*/
}
}
OS_EXIT_CRITICAL();
}
/*系统空闲任务*/
void IdleTask(void *pdata)
{
u32 IdleCount = 0;
while(1)
{
IdleCount++;
}
}
void OSTaskSwHook(void)
{
}
;/*********************** (C) COPYRIGHT 2013 Libraworks *************************
;* File Name : RTOS.h
;* Author : 卢晓铭
;* Version : V1.0
;* Date : 01/26/2013
;* Description : LXM-RTOS asm port
;*******************************************************************************/
#ifndef __RTOS_H
#define __RTOS_H
#include "stm32f10x.h"//加入头文件
typedef struct TaskCtrBlockHead /*任务控制块数据结构*/
{
u32 OSTCBStkPtr; /*保存任务栈顶*/
u32 OSTCBDly; /*任务延时时钟*/
}TaskCtrBlock;
#define OS_TASKS 32 /*总任务数*/
#define IdelTask_Prio 31 /*空闲任务优先级*/
extern TaskCtrBlock TCB[OS_TASKS - 1]; /*任务控制块定义*/
extern TaskCtrBlock *p_OSTCBCur; /*指向当前任务控制块的指针*/
extern TaskCtrBlock *p_OSTCBHighRdy; /*指向最高优先级就绪任务控制块的指针*/
extern u8 OSPrioCur; /*当前执行任务*/
extern u8 OSPrioHighRdy; /*最高优先级*/
extern u8 OSRunning; /*多任务运行标志0:为运行,1:运行*/
extern u32 OSInterruptSum; /*进入中断次数*/
extern u32 OSTime; /*系统时间(进入时钟中断次数)*/
extern u32 OSRdyTbl; /*任务就绪表,0表示挂起,1表示就绪*/
extern u32 OSIntNesting; /*任务嵌套数*/
void OSTimeDly(u32 ticks); /*设置任务延时时间*/
void TicksInterrupt(void); /*定时器中断对任务延时处理函数*/
void IdleTask(void *pdata); /*系统空闲任务*/
void OSSched(void); /*任务切换*/
void OSStart(void); /*多任务系统开始*/
void OSIntExit(void); /*中断退出函数*/
void OSTaskCreate(void (*Task)(void *parg), void *parg, u32 *p_Stack, u8 TaskID); /*创建任务函数*/
void OSTaskSuspend(u8 prio); /*挂起指定任务*/
void OSTaskResume(u8 prio); /*回复指定的挂起任务*/
void OSTaskSwHook(void); /*空函数*/
/*in asm function*/
void OS_EXIT_CRITICAL(void); /*退出临界区*/
void OS_ENTER_CRITICAL(void); /*进入临界区*/
void OSStartHighRdy(void); /*调度第一个任务*/
void OSCtxSw(void); /*函数级任务切换*/
void OSIntCtxSw(void); /*中断级任务切换*/
#endif
文件RTOS_ASM.s
;/*********************** (C) COPYRIGHT 2013 Libraworks *************************
;* File Name : RTOS_ASM.s
;* Author : 卢晓铭
;* Version : V1.0
;* Date : 01/26/2013
;* Description : LXM-RTOS asm port
;*******************************************************************************/
IMPORT OSInterruptSum
IMPORT OSRunning
IMPORT p_OSTCBCur
IMPORT p_OSTCBHighRdy
IMPORT OSTaskSwHook
IMPORT OSPrioCur
IMPORT OSPrioHighRdy
EXPORT OS_ENTER_CRITICAL
EXPORT OS_EXIT_CRITICAL
EXPORT OSStartHighRdy
EXPORT OSCtxSw
EXPORT OSIntCtxSw
EXPORT PendSV_Handler
NVIC_INT_CTRL EQU 0xE000ED04 ; 中断控制寄存器
NVIC_SYSPRI2 EQU 0xE000ED20 ; 系统优先级寄存器(2)
NVIC_PENDSV_PRI EQU 0xFFFF0000 ; 软件中断和系统节拍中断
; (都为最低,0xff).
NVIC_PENDSVSET EQU 0x10000000 ; 触发软件中断的值.
PRESERVE8
SECTION .text:CODE:NOROOT(2)
THUMB
;/***************************************************************************************
;* 函数名称: OS_ENTER_CRITICAL
;*
;* 功能描述: 进入临界区
;*
;* 参 数: None
;*
;* 返 回 值: None
;*****************************************************************************************/
OS_ENTER_CRITICAL
CPSID I ; Disable all the interrupts
PUSH {R1,R2}
LDR R1, =OSInterruptSum ; OSInterrputSum++
LDRB R2, [R1]
ADD R2, R2, #1
STRB R2, [R1]
POP {R1,R2}
BX LR
;/***************************************************************************************
;* 函数名称: OS_EXIT_CRITICAL
;*
;* 功能描述: 退出临界区
;*
;* 参 数: None
;*
;* 返 回 值: None
;*****************************************************************************************/
OS_EXIT_CRITICAL
PUSH {R1, R2}
LDR R1, =OSInterruptSum ; OSInterrputSum--
LDRB R2, [R1]
SUB R2, R2, #1
STRB R2, [R1]
MOV R1, #0
CMP R2, #0 ; if OSInterrputSum=0,enable
; interrupts如果OSInterrputSum=0,
;MSREQ PRIMASK, R1
CPSIE I
POP {R1, R2}
BX LR
;/**************************************************************************************
;* 函数名称: OSStartHighRdy
;*
;* 功能描述: 使用调度器运行第一个任务
;*
;* 参 数: None
;*
;* 返 回 值: None
;**************************************************************************************/
OSStartHighRdy
LDR R4, =NVIC_SYSPRI2 ; set the PendSV exception priority
LDR R5, =NVIC_PENDSV_PRI
STR R5, [R4]
MOV R4, #0 ; set the PSP to 0 for initial context switch call
MSR PSP, R4
LDR R4, =OSRunning ; OSRunning = TRUE
MOV R5, #1
STRB R5, [R4]
;切换到最高优先级的任务
LDR R4, =NVIC_INT_CTRL ;rigger the PendSV exception (causes context switch)
LDR R5, =NVIC_PENDSVSET
STR R5, [R4]
CPSIE I ;enable interrupts at processor level
OSStartHang
B OSStartHang ;should never get here
;/**************************************************************************************
;* 函数名称: OSCtxSw
;*
;* 功能描述: 函数级任务切换
;*
;* 参 数: None
;*
;* 返 回 值: None
;***************************************************************************************/
OSCtxSw
PUSH {R4, R5}
LDR R4, =NVIC_INT_CTRL ;触发PendSV异常 (causes context switch)
LDR R5, =NVIC_PENDSVSET
STR R5, [R4]
POP {R4, R5}
BX LR
;/**************************************************************************************
;* 函数名称: OSIntCtxSw
;*
;* 功能描述: 中断级任务切换
;*
;* 参 数: None
;*
;* 返 回 值: None
;***************************************************************************************/
OSIntCtxSw
PUSH {R4, R5}
LDR R4, =NVIC_INT_CTRL ;触发PendSV异常 (causes context switch)
LDR R5, =NVIC_PENDSVSET
STR R5, [R4]
POP {R4, R5}
BX LR
NOP
;/**************************************************************************************
;* 函数名称: OSPendSV
;*
;* 功能描述: OSPendSV is used to cause a context switch.
;*
;* 参 数: None
;*
;* 返 回 值: None
;***************************************************************************************/
PendSV_Handler
CPSID I ; Prevent interruption during context switch
MRS R0, PSP ; PSP is process stack pointer 如果在用PSP堆栈,则可以忽略保存寄存器,参考CM3权威中的双堆栈-白菜注
CBZ R0, PendSV_Handler_Nosave ; Skip register save the first time
SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack
STM R0, {R4-R11}
LDR R1, =p_OSTCBCur ; OSTCBCur->OSTCBStkPtr = SP;
LDR R1, [R1]
STR R0, [R1] ; R0 is SP of process being switched out
; At this point, entire context of process has been saved
PendSV_Handler_Nosave
PUSH {R14} ; Save LR exc_return value
LDR R0, =OSTaskSwHook ; OSTaskSwHook();
BLX R0
POP {R14}
LDR R0, =OSPrioCur ; OSPrioCur = OSPrioHighRdy;
LDR R1, =OSPrioHighRdy
LDRB R2, [R1]
STRB R2, [R0]
LDR R0, =p_OSTCBCur ; OSTCBCur = OSTCBHighRdy;
LDR R1, =p_OSTCBHighRdy
LDR R2, [R1]
STR R2, [R0]
LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr;
LDM R0, {R4-R11} ; Restore r4-11 from new process stack
ADDS R0, R0, #0x20
MSR PSP, R0 ; Load PSP with new process SP
ORR LR, LR, #0x04 ; Ensure exception return uses process stack
CPSIE I
BX LR ; Exception return will restore remaining context
;************************************************************
END