一步步寫STM32 OS【四】OS基本框架


一、上篇回顧

上一篇文章中,我們完成了兩個任務使用PendSV實現了互相切換的功能,下面我們接着其思路往下做。這次我們完成OS基本框架,即實現一個非搶占式(已經調度的進程執行完成,然后根據優先級調度等待的進程)的任務調度系統,至於搶占式的,就留給大家思考了。上次代碼中Task_Switch實現了兩個任務的切換,代碼如下:

void Task_Switch()
{
  if(g_OS_Tcb_CurP == &TCB_1)
    g_OS_Tcb_HighRdyP=&TCB_2;
  else
    g_OS_Tcb_HighRdyP=&TCB_1;
  OSCtxSw();
}

我們把要切換任務指針付給跟_OS_Tcb_HighRdyP,然后調用OSCtxSw觸發PendSV異常,就實現了任務的切換。如果是多個任務,我們只需找出就緒任務中優先級最大的切換之即可。

二、添加任務調度功能

為了實現這一目標我們至少需要知道任務的狀態和時間等數據。我們定義了一個任務狀態枚舉類型OS_TASK_STA,方便添加修改狀態。在OS_TCB結構體中添加了兩個成員TimeDly和State,TimeDly是為了實現OS_TimeDly,至於State與優先級一起是作為任務切換的依據。

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;

說到任務切換,我們必須面對臨界區的問題,在一些臨界的代碼兩端不加臨界區進去和退出代碼,會出現許多意想不到的問題。以下地方需要特別注意,對關鍵的全局變量的寫操作、對任務控制塊的操作等。進入臨界區和退出臨界區需要關閉和開啟中斷,我們采用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()

一個OS至少要有任務表,我們可以用數組,當然也可以用鏈表。為了簡單,我們使用數組,使用數組下表作為優先級。當然,必要的地方一定要做數組越界檢查。

#define OS_TASK_MAX_NUM 32
OS_TCBP OS_TCB_TABLE[OS_TASK_MAX_NUM];

為了使OS更完整,我們定義幾個全局變量,OS_TimeTick記錄系統時間,g_Prio_Cur記錄當前運行的任務優先級,g_Prio_HighRdy記錄任務調度后就緒任務中的最高優先級。

OS_U32 OS_TimeTick;
OS_U8 g_Prio_Cur; 
OS_U8 g_Prio_HighRdy;

 

下面三個函數與PendSV一起實現了任務的調度功能。

OS_Task_Switch函數功能:找出已就緒最高優先級的任務,並將其TCB指針賦值給g_OS_Tcb_HighRdyP,將其優先級賦值g_Prio_HighRdy。注意其中使用了臨界區。

void OS_Task_Switch(void)
{
  OS_S32 i;
  OS_TCBP tcb_p;
  OS_USE_CRITICAL
  for(i=0;i<OS_TASK_MAX_NUM;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至當前任務為延時狀態,並將延時時間賦值給當前TCB的TimeDly成員,並調用OS_Task_Switch函數,然后觸發PendSV進行上下文切換。OS_Task_Switch找到就緒狀態中優先級最高的,並將其賦值相關全局變量,作為上下文切換的依據。

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實現系統計時,並遍歷任務表,任務若是延時狀態,就令其延時值減一,若減完后為零,就將其置為就緒狀態。

void SysTick_Handler(void)
{
  OS_TCBP tcb_p;
  OS_S32 i;
  OS_USE_CRITICAL
    
  OS_ENTER_CRITICAL();
  ++OS_TimeTick;
    for(i=0;i<OS_TASK_MAX_NUM;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();
}

當所有任務都沒就緒怎么辦?這時就需要空閑任務了,我們把它設為優先級最低的任務。WFE指令為休眠指令,當來中斷時,退出休眠,然后看看有沒有已就緒的任務,有則調度之,否則繼續休眠,這樣可以減小功耗哦。

void OS_Task_Idle(void)
{
  while(1)
  {
    asm("WFE"); 
    OS_Task_Switch();
    OS_PendSV_Trigger();
  }
}

當一個任務只運行一次時(例如下面main.c的task1),結束時就會調用OS_Task_End函數,此函數會調用OS_Task_Delete函數從任務表中刪除當前的任務,然后調度任務。

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 End\n",g_Prio_Cur);
  OS_Task_Delete(g_Prio_Cur);
  OS_Task_Switch();
  OS_PendSV_Trigger();
}

 

三、OS實戰

下面是完整的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<OS_TASK_MAX_NUM;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 End\n",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<OS_TASK_MAX_NUM;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_TASK_MAX_NUM;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變化不大,具體內容可以下載文章末尾提供的工程參考。

老規矩,下載調試,全速運行,觀察Terminal IO窗口:

QQ截圖20131103215553

從輸出來看,我們已經完成了目標。但不保證穩定性,可能有不少Bugs。至此,可以說其實寫一個OS並不難,難的是寫一個穩定安全高效的OS。所以,現在只是走了一小步,想要完成一個成熟的OS,還需要不斷測試,不斷優化。例如,我們采用數組存儲任務表,也可以采用鏈表,各有優缺點。我們只有一個任務表,也可以分成多個表,例如就續表,等待表等等。我們的任務調度部分運行時間不確定,對於實時OS,這是不可以的,怎么修改呢,例如像uCOS的查找表法那樣。現在我們的系統只能創建並調度任務,還未加入其他功能,例如信號量、郵箱、隊列、內存管理等。其實到了這里,大家完全可以發揮自己的創造力,參照本文開發自己的OS。如果以后有時間的話,還會再寫幾篇文章繼續完善我們的OS。

四、工程下載

stepbystep_stm32_os_basic.rar


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM