一步步寫STM32 OS【四】OS基本框架
上一篇文章中,我們完成了兩個(gè)任務(wù)使用PendSV實(shí)現(xiàn)了互相切換的功能,下面我們接著其思路往下做。這次我們完成OS基本框架,即實(shí)現(xiàn)一個(gè)非搶占式(已經(jīng)調(diào)度的進(jìn)程執(zhí)行完成,然后根據(jù)優(yōu)先級(jí)調(diào)度等待的進(jìn)程)的任務(wù)調(diào)度系統(tǒng),至于搶占式的,就留給大家思考了。上次代碼中Task_Switch實(shí)現(xiàn)了兩個(gè)任務(wù)的切換,代碼如下:
本文引用地址:http://m.butianyuan.cn/article/201701/342938.htmvoid Task_Switch()
{
if(g_OS_Tcb_CurP == &TCB_1)
g_OS_Tcb_HighRdyP=&TCB_2;
else
g_OS_Tcb_HighRdyP=&TCB_1;
OSCtxSw();
}
我們把要切換任務(wù)指針付給跟_OS_Tcb_HighRdyP,然后調(diào)用OSCtxSw觸發(fā)PendSV異常,就實(shí)現(xiàn)了任務(wù)的切換。如果是多個(gè)任務(wù),我們只需找出就緒任務(wù)中優(yōu)先級(jí)最大的切換之即可。
二、添加任務(wù)調(diào)度功能
為了實(shí)現(xiàn)這一目標(biāo)我們至少需要知道任務(wù)的狀態(tài)和時(shí)間等數(shù)據(jù)。我們定義了一個(gè)任務(wù)狀態(tài)枚舉類型OS_TASK_STA,方便添加修改狀態(tài)。在OS_TCB結(jié)構(gòu)體中添加了兩個(gè)成員TimeDly和State,TimeDly是為了實(shí)現(xiàn)OS_TimeDly,至于State與優(yōu)先級(jí)一起是作為任務(wù)切換的依據(jù)。
typedef enum OS_TASK_STA
{
TASK_READY,
TASK_DELAY,
} OS_TASK_STA;
typedef struct OS_TCB
{
OS_STK *StkAddr;
OS_U32 TimeDly;
OS_TASK_STA State;
}OS_TCB,*OS_TCBP;
說到任務(wù)切換,我們必須面對(duì)臨界區(qū)的問題,在一些臨界的代碼兩端不加臨界區(qū)進(jìn)去和退出代碼,會(huì)出現(xiàn)許多意想不到的問題。以下地方需要特別注意,對(duì)關(guān)鍵的全局變量的寫操作、對(duì)任務(wù)控制塊的操作等。進(jìn)入臨界區(qū)和退出臨界區(qū)需要關(guān)閉和開啟中斷,我們采用uCOS中的一部分代碼:
PUBLIC OS_CPU_SR_Save
PUBLIC OS_CPU_SR_Restore
OS_CPU_SR_Save
MRS R0, PRIMASK
CPSID I
BX LR
OS_CPU_SR_Restore
MSR PRIMASK, R0
BX LR
#define OS_USE_CRITICAL OS_U32 cpu_sr;
#define OS_ENTER_CRITICAL() {cpu_sr = OS_CPU_SR_Save();}
#define OS_EXIT_CRITICAL() {OS_CPU_SR_Restore(cpu_sr);}
#define OS_PendSV_Trigger() OSCtxSw()
一個(gè)OS至少要有任務(wù)表,我們可以用數(shù)組,當(dāng)然也可以用鏈表。為了簡(jiǎn)單,我們使用數(shù)組,使用數(shù)組下表作為優(yōu)先級(jí)。當(dāng)然,必要的地方一定要做數(shù)組越界檢查。
#define OS_TASK_MAX_NUM 32
OS_TCBP OS_TCB_TABLE[OS_TASK_MAX_NUM];
為了使OS更完整,我們定義幾個(gè)全局變量,OS_TimeTick記錄系統(tǒng)時(shí)間,g_Prio_Cur記錄當(dāng)前運(yùn)行的任務(wù)優(yōu)先級(jí),g_Prio_HighRdy記錄任務(wù)調(diào)度后就緒任務(wù)中的最高優(yōu)先級(jí)。
OS_U32 OS_TimeTick;
OS_U8 g_Prio_Cur;
OS_U8 g_Prio_HighRdy;
下面三個(gè)函數(shù)與PendSV一起實(shí)現(xiàn)了任務(wù)的調(diào)度功能。
OS_Task_Switch函數(shù)功能:找出已就緒最高優(yōu)先級(jí)的任務(wù),并將其TCB指針賦值給g_OS_Tcb_HighRdyP,將其優(yōu)先級(jí)賦值g_Prio_HighRdy。注意其中使用了臨界區(qū)。
void OS_Task_Switch(void)
{
OS_S32 i;
OS_TCBP tcb_p;
OS_USE_CRITICAL
for(i=0;i
{
tcb_p=OS_TCB_TABLE[i];
if(tcb_p == NULL) continue;
if(tcb_p->State==TASK_READY) break;
}
OS_ENTER_CRITICAL();
g_OS_Tcb_HighRdyP=tcb_p;
g_Prio_HighRdy=i;
OS_EXIT_CRITICAL();
}
OS_TimeDly至當(dāng)前任務(wù)為延時(shí)狀態(tài),并將延時(shí)時(shí)間賦值給當(dāng)前TCB的TimeDly成員,并調(diào)用OS_Task_Switch函數(shù),然后觸發(fā)PendSV進(jìn)行上下文切換。OS_Task_Switch找到就緒狀態(tài)中優(yōu)先級(jí)最高的,并將其賦值相關(guān)全局變量,作為上下文切換的依據(jù)。
void OS_TimeDly(OS_U32 ticks)
{
OS_USE_CRITICAL
OS_ENTER_CRITICAL();
g_OS_Tcb_CurP->State=TASK_DELAY;
g_OS_Tcb_CurP->TimeDly=ticks;
OS_EXIT_CRITICAL();
OS_Task_Switch();
OS_PendSV_Trigger();
}
SysTick_Handler實(shí)現(xiàn)系統(tǒng)計(jì)時(shí),并遍歷任務(wù)表,任務(wù)若是延時(shí)狀態(tài),就令其延時(shí)值減一,若減完后為零,就將其置為就緒狀態(tài)。
void SysTick_Handler(void)
{
OS_TCBP tcb_p;
OS_S32 i;
OS_USE_CRITICAL
OS_ENTER_CRITICAL();
++OS_TimeTick;
for(i=0;i
{
tcb_p=OS_TCB_TABLE[i];
if(tcb_p == NULL) continue;
if(tcb_p->State==TASK_DELAY)
{
--tcb_p->TimeDly;
if(tcb_p->TimeDly == 0)
tcb_p->State=TASK_READY;
}
}
OS_EXIT_CRITICAL();
}
當(dāng)所有任務(wù)都沒就緒怎么辦?這時(shí)就需要空閑任務(wù)了,我們把它設(shè)為優(yōu)先級(jí)最低的任務(wù)。WFE指令為休眠指令,當(dāng)來中斷時(shí),退出休眠,然后看看有沒有已就緒的任務(wù),有則調(diào)度之,否則繼續(xù)休眠,這樣可以減小功耗哦。
void OS_Task_Idle(void)
{
while(1)
{
asm("WFE");
OS_Task_Switch();
OS_PendSV_Trigger();
}
}
當(dāng)一個(gè)任務(wù)只運(yùn)行一次時(shí)(例如下面main.c的task1),結(jié)束時(shí)就會(huì)調(diào)用OS_Task_End函數(shù),此函數(shù)會(huì)調(diào)用OS_Task_Delete函數(shù)從任務(wù)表中刪除當(dāng)前的任務(wù),然后調(diào)度任務(wù)。
void OS_Task_Delete(OS_U8 prio)
{
if(prio >= OS_TASK_MAX_NUM) return;
OS_TCB_TABLE[prio]=0;
}
void OS_Task_End(void)
{
printf("Task of Prio %d Endn",g_Prio_Cur);
OS_Task_Delete(g_Prio_Cur);
OS_Task_Switch();
OS_PendSV_Trigger();
}
三、OS實(shí)戰(zhàn)
下面是完整的main.c代碼:
#include "stdio.h"
#include "stm32f4xx.h"
#define OS_EXCEPT_STK_SIZE 1024
#define TASK_1_STK_SIZE 128
#define TASK_2_STK_SIZE 128
#define TASK_3_STK_SIZE 128
#define TASK_IDLE_STK_SIZE 1024
#define OS_TASK_MAX_NUM 32
#define OS_TICKS_PER_SECOND 1000
#define OS_USE_CRITICAL OS_U32 cpu_sr;
#define OS_ENTER_CRITICAL() {cpu_sr = OS_CPU_SR_Save();}
#define OS_EXIT_CRITICAL() {OS_CPU_SR_Restore(cpu_sr);}
#define OS_PendSV_Trigger() OSCtxSw()
typedef signed char OS_S8;
typedef signed short OS_S16;
typedef signed int OS_S32;
typedef unsigned char OS_U8;
typedef unsigned short OS_U16;
typedef unsigned int OS_U32;
typedef unsigned int OS_STK;
typedef void (*OS_TASK)(void);
typedef enum OS_TASK_STA
{
TASK_READY,
TASK_DELAY,
} OS_TASK_STA;
typedef struct OS_TCB
{
OS_STK *StkAddr;
OS_U32 TimeDly;
OS_U8 State;
}OS_TCB,*OS_TCBP;
OS_TCBP OS_TCB_TABLE[OS_TASK_MAX_NUM];
OS_TCBP g_OS_Tcb_CurP;
OS_TCBP g_OS_Tcb_HighRdyP;
OS_U32 OS_TimeTick;
OS_U8 g_Prio_Cur;
OS_U8 g_Prio_HighRdy;
static OS_STK OS_CPU_ExceptStk[OS_EXCEPT_STK_SIZE];
OS_STK *g_OS_CPU_ExceptStkBase;
static OS_TCB TCB_1;
static OS_TCB TCB_2;
static OS_TCB TCB_3;
static OS_TCB TCB_IDLE;
static OS_STK TASK_1_STK[TASK_1_STK_SIZE];
static OS_STK TASK_2_STK[TASK_2_STK_SIZE];
static OS_STK TASK_3_STK[TASK_3_STK_SIZE];
static OS_STK TASK_IDLE_STK[TASK_IDLE_STK_SIZE];
extern OS_U32 SystemCoreClock;
extern void OSStart_Asm(void);
extern void OSCtxSw(void);
extern OS_U32 OS_CPU_SR_Save(void);
extern void OS_CPU_SR_Restore(OS_U32);
void task_1(void);
void task_2(void);
void task_3(void);
void OS_Task_Idle(void);
void OS_TimeDly(OS_U32);
void OS_Task_Switch(void);
void OS_Task_Create(OS_TCB *,OS_TASK,OS_STK *,OS_U8);
void OS_Task_Delete(OS_U8);
void OS_Task_End(void);
void OS_Init(void);
void OS_Start(void);
void task_1(void)
{
printf("[%d]Task 1 Runing!!!n",OS_TimeTick);
OS_Task_Create(&TCB_2,task_2,&TASK_2_STK[TASK_2_STK_SIZE-1],5);
OS_Task_Create(&TCB_3,task_3,&TASK_3_STK[TASK_3_STK_SIZE-1],7);
}
void task_2(void)
{
while(1)
{
printf("[%d]Task 2 Runing!!!n",OS_TimeTick);
OS_TimeDly(1000);
}
}
void task_3(void)
{
while(1)
{
printf("[%d]Task 3 Runing!!!n",OS_TimeTick);
OS_TimeDly(1500);
}
}
void OS_Task_Idle(void)
{
while(1)
{
asm("WFE");
OS_Task_Switch();
OS_PendSV_Trigger();
}
}
void OS_TimeDly(OS_U32 ticks)
{
OS_USE_CRITICAL
OS_ENTER_CRITICAL();
g_OS_Tcb_CurP->State=TASK_DELAY;
g_OS_Tcb_CurP->TimeDly=ticks;
OS_EXIT_CRITICAL();
OS_Task_Switch();
OS_PendSV_Trigger();
}
void OS_Task_Switch(void)
{
OS_S32 i;
OS_TCBP tcb_p;
OS_USE_CRITICAL
for(i=0;i
{
tcb_p=OS_TCB_TABLE[i];
if(tcb_p == NULL) continue;
if(tcb_p->State==TASK_READY) break;
}
OS_ENTER_CRITICAL();
g_OS_Tcb_HighRdyP=tcb_p;
g_Prio_HighRdy=i;
OS_EXIT_CRITICAL();
}
void OS_Task_Delete(OS_U8 prio)
{
if(prio >= OS_TASK_MAX_NUM) return;
OS_TCB_TABLE[prio]=0;
}
void OS_Task_End(void)
{
printf("Task of Prio %d Endn",g_Prio_Cur);
OS_Task_Delete(g_Prio_Cur);
OS_Task_Switch();
OS_PendSV_Trigger();
}
void OS_Task_Create(OS_TCB *tcb,OS_TASK task,OS_STK *stk,OS_U8 prio)
{
OS_USE_CRITICAL
OS_STK *p_stk;
if(prio >= OS_TASK_MAX_NUM) return;
OS_ENTER_CRITICAL();
p_stk = stk;
p_stk = (OS_STK *)((OS_STK)(p_stk) & 0xFFFFFFF8u);
*(--p_stk) = (OS_STK)0x01000000uL; //xPSR
*(--p_stk) = (OS_STK)task; // Entry Point
*(--p_stk) = (OS_STK)OS_Task_End; // R14 (LR)
*(--p_stk) = (OS_STK)0x12121212uL; // R12
*(--p_stk) = (OS_STK)0x03030303uL; // R3
*(--p_stk) = (OS_STK)0x02020202uL; // R2
*(--p_stk) = (OS_STK)0x01010101uL; // R1
*(--p_stk) = (OS_STK)0x00000000u; // R0
*(--p_stk) = (OS_STK)0x11111111uL; // R11
*(--p_stk) = (OS_STK)0x10101010uL; // R10
*(--p_stk) = (OS_STK)0x09090909uL; // R9
*(--p_stk) = (OS_STK)0x08080808uL; // R8
*(--p_stk) = (OS_STK)0x07070707uL; // R7
*(--p_stk) = (OS_STK)0x06060606uL; // R6
*(--p_stk) = (OS_STK)0x05050505uL; // R5
*(--p_stk) = (OS_STK)0x04040404uL; // R4
tcb->StkAddr=p_stk;
tcb->TimeDly=0;
tcb->State=TASK_READY;
OS_TCB_TABLE[prio]=tcb;
OS_EXIT_CRITICAL();
}
void SysTick_Handler(void)
{
OS_TCBP tcb_p;
OS_S32 i;
OS_USE_CRITICAL
OS_ENTER_CRITICAL();
++OS_TimeTick;
for(i=0;i
{
tcb_p=OS_TCB_TABLE[i];
if(tcb_p == NULL) continue;
if(tcb_p->State==TASK_DELAY)
{
--tcb_p->TimeDly;
if(tcb_p->TimeDly == 0)
tcb_p->State=TASK_READY;
}
}
OS_EXIT_CRITICAL();
}
void OS_Init(void)
{
int i;
g_OS_CPU_ExceptStkBase = OS_CPU_ExceptStk + OS_EXCEPT_STK_SIZE - 1;
asm("CPSID I");
for(i=0;i
OS_TCB_TABLE[i]=0;
OS_TimeTick=0;
OS_Task_Create(&TCB_IDLE,OS_Task_Idle,&TASK_IDLE_STK[TASK_IDLE_STK_SIZE-1],OS_TASK_MAX_NUM-1);
}
void OS_Start(void)
{
OS_Task_Switch();
SystemCoreClockUpdate();
SysTick_Config(SystemCoreClock/OS_TICKS_PER_SECOND);
OSStart_Asm();
}
int main()
{
OS_Init();
OS_Task_Create(&TCB_1,task_1,&TASK_1_STK[TASK_1_STK_SIZE-1],2);
OS_Start();
return 0;
}
os_port.asm變化不大,具體內(nèi)容可以下載文章末尾提供的工程參考。
老規(guī)矩,下載調(diào)試,全速運(yùn)行,觀察Terminal IO窗口:
從輸出來看,我們已經(jīng)完成了目標(biāo)。但不保證穩(wěn)定性,可能有不少Bugs。至此,可以說其實(shí)寫一個(gè)OS并不難,難的是寫一個(gè)穩(wěn)定安全高效的OS。所以,現(xiàn)在只是走了一小步,想要完成一個(gè)成熟的OS,還需要不斷測(cè)試,不斷優(yōu)化。例如,我們采用數(shù)組存儲(chǔ)任務(wù)表,也可以采用鏈表,各有優(yōu)缺點(diǎn)。我們只有一個(gè)任務(wù)表,也可以分成多個(gè)表,例如就續(xù)表,等待表等等。我們的任務(wù)調(diào)度部分運(yùn)行時(shí)間不確定,對(duì)于實(shí)時(shí)OS,這是不可以的,怎么修改呢,例如像uCOS的查找表法那樣。現(xiàn)在我們的系統(tǒng)只能創(chuàng)建并調(diào)度任務(wù),還未加入其他功能,例如信號(hào)量、郵箱、隊(duì)列、內(nèi)存管理等。其實(shí)到了這里,大家完全可以發(fā)揮自己的創(chuàng)造力,參照本文開發(fā)自己的OS。如果以后有時(shí)間的話,還會(huì)再寫幾篇文章繼續(xù)完善我們的OS。
評(píng)論