1. 让大家明白OS原理
2. 编译原理及程序运行原理入门
3. Cortex M3指令集等基础知识入门
下面是操作系统任务相关的函数接口
/*==================================================================
* Function : OS_TASK_CreateTask
* Description : 创建新任务
* Input Para :
IN U8 id, 任务号,同任务优先级,每个任务唯一
IN TaskFunction_t taskHandle, 任务函数,任务入口
IN U16 taskStackDeep, 任务的最大堆栈深度,sizeof(StackSize_t)*taskStackDeep=实际占内存字节数
IN U32 *eventBitMap 任务事件位图,用于任务通信,不需要可以填NULL
* Output Para : 无
* Return Value:
OS_OK 创建成功
OS_ERROR 创建失败
==================================================================*/
extern S32 OS_TASK_CreateTask
(
IN U8 id,
IN TaskFunction_t taskHandle,
IN U16 taskStackDeep,
IN U32 *eventBitMap
);
/*==================================================================
* Function : OS_TASK_SchedulerTask
* Description : 启动任务调度
* Input Para : 无
* Output Para : 无
* Return Value: 无
==================================================================*/
extern VOID OS_TASK_SchedulerTask(VOID);
/*==================================================================
* Function : OS_TASK_TaskDelay
* Description : 用于阻塞任务等待超时
* Input Para : IN U16 ms 阻塞毫秒数
* Output Para : 无
* Return Value: 无
==================================================================*/
extern VOID OS_TASK_TaskDelay(IN U16 ms);
/*==================================================================
* Function : OS_TASK_WaitForEvent
* Description : 用于阻塞任务等待事件
* Input Para : 无
* Output Para : 无
* Return Value: 无
==================================================================*/
extern VOID OS_TASK_WaitForEvent(VOID);
首先,需要理解CPU是如何运行程序的,然后,理解操作系统是如何完成任务切换的。下面我们从这3个问题出发去理解:
代码编译后发生了什么呢?
答:当我们打开map文件时,我们就会理解,代码编译连接的过程其实是把我们写每一行代码都映射到代码空间上地址上的一个过程,最终生产的bin文件就是代码段的完全映射。如下图
把编译好的bin文件烧录到CPU上,CPU发生了什么?
答:烧录过程,只是把bin文件完整的写入到Flash上而已
上电后,CPU又产生了什么变化?
答:
上面的例子可以理解,单个任务时CPU是如何运作的,那么当多个任务时,我们只要把上面用到的寄存器和堆栈,每个任务复制一份,独立存储访问,然后切换PC指针和堆栈指针就可以完成任务的调度切换。如下图
如下图,任务必须严格按这三个状态切换
static StackSize_t* TASK_TaskStackFirstInit(IN StackSize_t *topStack, IN TaskFunction_t func)
{
/* 按堆栈地址顺序入栈,而非寄存器入栈顺序
* PSR,PC,LR,R12,R3,R2,R1,R0 以上是芯片自动入栈的
* R4,R5,R6,R7,R8,R9,R10,R11 以上手工入栈,入出栈顺序注意保持一致
* 此处也可以增加计数,用于堆栈溢出检查
*/
topStack--;
*topStack = OS_TASK_INITIAL_XPSR;
topStack--;
*topStack = (((StackSize_t)func) & OS_TASK_START_ADDRESS_MASK);
topStack--; /* 任务栈初次初始化,已是最上层了,返回即错,因此可以增加返回函数用户调试 */
topStack -= 5; /* 可用于函数入参 */
topStack -= 8;
return topStack;
}
S32 OS_TASK_CreateTask
(
IN U8 id,
IN TaskFunction_t taskHandle,
IN U16 taskStackDeep,
IN U32 *eventBitMap
)
{
TCB_S *newTcb = NULL;
StackSize_t *topStack = NULL;
if (id >= OS_TASK_MAX)
{
return OS_ERROR;
}
newTcb = (TCB_S*)malloc(sizeof(TCB_S));
if (NULL == newTcb)
{
return OS_ERROR;
}
newTcb->state = TASK_INIT;
topStack = (StackSize_t *)malloc(sizeof(StackSize_t)*taskStackDeep);
if (NULL == topStack)
{
return OS_ERROR;
}
topStack += sizeof(StackSize_t)*taskStackDeep;
newTcb->topStack = TASK_TaskStackFirstInit(topStack, taskHandle);
newTcb->state = TASK_READY;
newTcb->delay = 0;
newTcb->delayMax = 0;
newTcb->eventBitMap = eventBitMap;
newTcb->id = id;
gTaskTcbList[id] = newTcb;
return OS_OK;
}
该流程的目的是把CPU的控制权,有特权级线程模式切成用户级线程模式,根据OS原理分析得知,需要做如下处理
找到当前优先级最高且处于Ready状态的任务,即gCurrentTCB指向的任务
触发svc 0,进入SVC中断,此时处于handler模式
PSP指向当前任务的堆栈指针
利用LR寄存器异常返回特性,返回到线程模式使用线程堆栈,完成CPU控制权交接给当前任务,如图
__asm static VOID TASK_SvcHandler(VOID)
{
extern gCurrentTCB;
/* 任务相关内容映射到入线程栈 */
ldr r3, =gCurrentTCB
ldr r1, [r3]
ldr r0, [r1]
ldmia r0!, {r4-r11}
msr psp, r0
isb
/* 利用LR寄存器异常返回进入线程模式特性 */
mov r14, #0xfffffffd
bx r14
nop
}
void SVC_Handler(void)
{
TASK_GetCurrentTask();
TASK_SvcHandler();
}
__asm static VOID TASK_StartFirstTask(VOID)
{
/* 触发svc,在svc中断中通过修改LD寄存器值的方式进入线程模式 */
svc 0
nop
nop
}
VOID OS_TASK_SchedulerTask(VOID)
{
TASK_StartFirstTask();
return;
}
当存在多个任务时,每隔任务都需要轮流取得CPU控制权,从而达到并行运作的效果,目前设计的是每隔10ms切换一次,实现流程如下:
#define TASK_NVIC_INT_CTRL_REG ( * ( ( volatile uint32_t * ) 0xe000ed04 ) )
#define TASK_NVIC_PENDSVSET_BIT ( 1UL << 28UL )
/* ICSR寄存器bit28置1,触发PendSV中断 */
#define OS_TASK_SWITCH TASK_NVIC_INT_CTRL_REG = TASK_NVIC_PENDSVSET_BIT
VOID SysTick_Handler(VOID)
{
TASK_DelayList(); /* 本例中忽略 */
TASK_WaitForEventList(); /* 本例中忽略 */
gTaskSysTickCount++;
if ((gTaskSysTickCount%OS_TASK_SWITCH_INTERVAL) != 0)
{
return;
}
OS_TASK_SWITCH;
}
__asm VOID PendSV_Handler(VOID)
{
extern gCurrentTCB;
extern TASK_GetCurrentTask;
/* 把当前任务入栈,主要是R4-R11,因为其它已自动入栈 */
mrs r0, psp
isb
stmdb r0!, {r4-r11}
dsb
isb
/* 把堆栈地址映射到TCB */
ldr r3, =gCurrentTCB
ldr r2, [r3] /* r2 = gCurrentTCB*/
str r0, [r2] /* 把r0赋值给gCurrentTCB->topStack */
/* 切换任务上下文,注意堆栈保存,R3, r14需要重新恢复*/
stmdb sp!, {r3,r14}
dsb
isb
bl TASK_GetCurrentTask
ldmia sp!, {r3,r14}
dsb
isb
/* 获取新任务栈 */
ldr r1, [r3]
ldr r0, [r1]
ldmia r0!, {r4-r11}
dsb
isb
msr psp, r0
isb
bx r14
nop
}
经常需要某任务等待一段时间后再继续运行,即延时,其中等待的这段时间内,其它任务可以运行,从而充分利用CPU资源,流程如下:
保存需要等待的时间到任务控制块中,任务状态置成SUSPENDED,并释放CPU控制权
在systick 中断中,轮询每个任务,检测是否超时,
VOID OS_TASK_TaskDelay(IN U16 ms)
{
if ((0 == gCurrentTCB->delay) && (0 == gCurrentTCB->delayMax))
{
gCurrentTCB->delayMax = ms;
gCurrentTCB->delay = gTaskSysTickCount;
gCurrentTCB->state = TASK_SUSPENDED;
OS_TASK_SWITCH;
}
}
static VOID TASK_DelayList(VOID)
{
volatile TCB_S *tmpTcb = NULL;
U8 id = 0;
for (id = 0; id < OS_TASK_MAX; id++)
{
tmpTcb = gTaskTcbList[id];
if (NULL == tmpTcb)
{
continue;
}
if (tmpTcb->delayMax != 0)
{
if ((gTaskSysTickCount - tmpTcb->delay) >= tmpTcb->delayMax)
{
tmpTcb->delay = 0;
tmpTcb->delayMax = 0;
tmpTcb->state = TASK_READY;
OS_TASK_SWITCH;
return;
}
else
{
tmpTcb->state = TASK_SUSPENDED;
}
}
}
return;
}
经常会遇到这样的需求,一个任务期待在另一个任务触发了某事件后,才执行后续操作,在等待期间,CPU控制权由其他任务占用,提供CPU利用率。流程如下:
事件需要用户申请一个U32类型的全局变量,注意是位图,在任务创建时,填入到任务创建接口的eventBitMap参数,注意,必须传地址。extern S32 OS_TASK_CreateTask(IN U8 id, IN TaskFunction_t taskHandle, IN U16 taskStackDeep, IN U32 *eventBitMap);
在当前任务中需要等待的地方,调用OS_TASK_WaitForEvent函数等待,当事件满足时,该函数后面的代码才会被执行
OS会在在systick 中断中,轮询每个任务,检测是否收到事件,
VOID OS_TASK_WaitForEvent(VOID)
{
if (NULL == gCurrentTCB->eventBitMap)
{
return;
}
if (0 == *gCurrentTCB->eventBitMap)
{
gCurrentTCB->state = TASK_SUSPENDED;
OS_TASK_SWITCH;
}
}
static VOID TASK_WaitForEventList(VOID)
{
volatile TCB_S *tmpTcb = NULL;
U8 id = 0;
for (id = 0; id < OS_TASK_MAX; id++)
{
tmpTcb = gTaskTcbList[id];
if (NULL == tmpTcb)
{
continue;
}
if (NULL == tmpTcb->eventBitMap)
{
continue;
}
if (*tmpTcb->eventBitMap != 0)
{
tmpTcb->state = TASK_READY;
OS_TASK_SWITCH;
}
else
{
tmpTcb->state = TASK_SUSPENDED;
}
}
}
任务总有先后,优先级必不可少,期望当多个任务都进入READY时,可以优先执行优先级最高的任务,当最高优先级的任务转为SUSPENDED状态后,在执行次优先级的任务,用如此简单的设定实现优先调度的目的,如下:
在任务切换,获取任务上下文时,会调用下面的函数,该函数会从头开始变量任务,找到第一个READY状态的任务,使它进入RUNNING状态,获取CPU控制权。
可见,优先级同任务ID,ID越小优先级越高
VOID TASK_GetCurrentTask(VOID)
{
volatile TCB_S *tmpTcb = NULL;
U8 id = 0;
for (id = 0; id < OS_TASK_MAX; id++)
{
tmpTcb = gTaskTcbList[id];
if ((TASK_READY == tmpTcb->state) || (TASK_RUNNING == tmpTcb->state))
{
tmpTcb->state = TASK_RUNNING;
gCurrentTCB = tmpTcb;
break;
}
}
return;
}
《Cortex-M3权威指南Cn.pdf》
《GD32F10xCH_V1.1.pdf》
https://github.com/YaFood/GD32F103/tree/master/TestOS