欢迎光临
梦想从学习开始!

动手写简单的嵌入式操作系统一| 小熊测试

本文主要介绍 动手写简单的嵌入式操作系统一| 小熊测试,小熊希望对大家的学习或者工作具有一定的参考学习价值,在测试领域有所提升和发展。

  业余时间想研究一下RTOS,但是现有的嵌入式系统很多,代码量也很大,厚厚的一本书,又是任务控制块,又是链表又是指针的指来指去,让人不耐心点根本看不下去,也没太多时间去研究。于是就有了自己动手去做的想法,这样可以提高兴趣.比看书有意思。慢慢的发现,操作系统也没有那么神秘。触发软中断,保存堆栈,开始进行任务切换。于是一个多任务就出来了,但是一个完整的操作系统并不简单,涉及到一系列的算法和数据结构的运用,还有系统的引导程序bootloader,内存管理,文件系统,网络管理,IO驱动管理等模块。   有了想法,接下来就是付诸行动。但是还得学习汇编,这成了最大阻碍,工作任务多,下班后,没太多精力去学习它。不过只要能看的懂就可以。于是把ucos/II在stm32上移植部分的汇编代码招搬过来直接利用。这样可以把主要精力放在任务调度和任务间的同步和通讯上。这次任务创建和调度的原理很简单,效率肯定也不高。以后有更好的想法了,打算改进一下任务的调度算法,比如可以利用linux内核中的list_head双向循环链表,加入就绪队列和任务延时队列。利用keilRTX系统中的内存分配机制,动态allox()分配任务的TCB控制块,总之,多学习好的系统中的思想。   以下是汇编的代码OS_CPU_A.ASM,完成任务之间的切换和堆栈的保存。   主要是两个堆栈指针OSCurTCB,OSNewTCB。可以把主要精力放在用c写任务调度和任务间的同步与通信,   利用以下汇编代码,就可以暂时不管汇编部分,该任务调度时调用OSCtxSw(),传递最高优先级任务的堆栈指针给OSNewTCB,完成两个任务切换。 ;/*********************** (C) COPYRIGHT 2010 Libraworks *************************

;* File Name : os_cpu_a.asm

;* Author  : Librae

;* Version  : V1.0

;* Date   : 06/10/2010

;* Description : μCOS-II asm port for STM32

;*******************************************************************************/

  IMPORT  OSCurTCB                    ; External references

  IMPORT  OSNewTCB

  IMPORT  OSTaskSwHook

                 

  EXPORT  OSStartTask

  EXPORT  OSCtxSw

  EXPORT  OSIntCtxSw

  EXPORT  OS_CPU_SR_Save                       ; Functions declared in this file

  EXPORT  OS_CPU_SR_Restore     

  EXPORT  PendSV_Handler

   

NVIC_PENDSV_PRI  EQU     0xFFFF0000               ; PendSV priority value (lowest)

NVIC_PENDSVSET   EQU     0x10000000               ; value to trigger PendSV exception

NVIC_INT_CTRL    EQU     0xE000ED04               ; interrupt control state register

NVIC_SYSPRI2     EQU     0xE000ED20               ; system priority register (2)   PRESERVE8

 

  AREA    |.text|, CODE, READONLY

        THUMB

  

         

;********************************************************************************************************

;                                   CRITICAL SECTION METHOD 3 FUNCTIONS

;

;********************************************************************************************************

OS_CPU_SR_Save

    MRS     R0, PRIMASK   ;读取PRIMASK到R0,R0为返回值

    CPSID   I    ;PRIMASK=1,关中断(NMI和硬件FAULT可以响应)

    BX      LR       ;返回

OS_CPU_SR_Restore

    MSR     PRIMASK, R0     ;读取R0到PRIMASK中,R0为参数;open interrupt

    BX      LR    ;返回 ;/**************************************************************************************

;* 函数名称: OSStartTask

;*

;* 功能描述: 使用调度器运行第一个任务

;*

;* 参    数: None

;*

;* 返 回 值: None

;**************************************************************************************/

OSStartTask

        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, =NVIC_INT_CTRL     ;rigger the PendSV exception (causes context switch)

        LDR     R5, =NVIC_PENDSVSET    ;触发PendSV异常 (causes context switch)

        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

  NOP
;/**************************************************************************************

;* 函数名称: 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         

        STM     R0, {R4-R11}                        ; Save remaining regs r4-11 on process stack

        LDR     R1, =OSCurTCB    

        LDR     R1, [R1]

        STR     R0, [R1]                             ; R0 is SP of process being switched out          

PendSV_Handler_Nosave

        PUSH    {R14}                                ; Save LR exc_return value

        LDR     R0, =OSTaskSwHook                    ; OSTaskSwHook();

        BLX     R0

        POP     {R14}

        LDR     R0, =OSCurTCB                         ;OSCurTCB=OSNewTCB;     

        LDR     R1, =OSNewTCB

        LDR     R2, [R1]

        STR     R2, [R0]

        LDR     R0, [R2]                              ; R0 is new process SP; SP = OSCurTCB;       

        LDM     R0, {R4-R11}                ; Restore r4-11 from new process stac

        ADD     R0, R0, #0x20

        MSR     PSP, R0             

        ORR     LR, LR, #0x04

  CPSIE   I                                 ; Exception return will restore remaining context

        BX      LR                   

  end   接下来定义一个任务控制块: typedef struct taskControlBlock

{

 /*当前的栈顶指针*/

 OS_STK     *pStackTop;

 /*当前优先级*/

 PRIO_TYPE    CurPriority;

 /*任务状态*/

 uint8      TaskStat;

 /*等待时间片的个数*/

 int32     TCBDelay;

 

} TCB;

/*当前运行的任务*/

TCB   *OSCurTCB;

/*当前准备新运行的任务*/

TCB   *OSNewTCB;

/*当前OS中所有的任务*/

uint8  TaskNUM=0;

TCB   OSTCBTable[MAX_TASK_NUM];
  OSCurTCB和OSNewTCB分别是当前运行任务的堆栈指针和要运行的新任务的堆栈指针。   下一步就是任务的创建了,任务是如何创建的。每个任务都有自己的堆栈空间,就像是单独占用CPU一样,所以创建任务还需完成任务堆栈的初始化。   需要知道CPU是如何出栈压栈的,保存了哪些寄存器,顺序是什么,堆栈的增长方向是什么。参考《cortexM3权威指南》,书中有详细的介绍。   以下是c语言写的任务堆栈的初始化函数,位于文件OS_CPU.c中,如果需要移植,除了汇编部分OS_CPU_A.asm文件修改外,OS_CPU.c和OS_TYPE.h等文件也需要修改。仅这几个文件。   OS_STK实际上就是int32,因为stm32上堆栈指针就是32位长度。第一个参数是任务的地址,即函数的地址,第二个参数是任务的堆栈指针。 OS_STK *OSTaskStkInit (void (*task),OS_STK *ptos)

{

    OS_STK *stk;

    stk    = ptos;                          /* get stack point       */

    *(stk)    = (uint32)0x01000000L;        /* xPSR                  */

    *(–stk)  = (uint32)task;               /* Entry Point           */

    *(–stk)  = (uint32)0xFFFFFFFEL;        /* R14 (LR)              */

    *(–stk)  = (uint32)0x12121212L;        /* R12                   */

    *(–stk)  = (uint32)0x03030303L;        /* R3                    */

    *(–stk)  = (uint32)0x02020202L;        /* R2                    */

    *(–stk)  = (uint32)0x01010101L;        /* R1                    */

    *(–stk)  = (uint32)0;                  /* R0 : argument         */

                                            /* Remaining registers   */

    *(–stk)  = (uint32)0x11111111L;        /* R11                   */

    *(–stk)  = (uint32)0x10101010L;        /* R10                   */

    *(–stk)  = (uint32)0x09090909L;        /* R9                    */

    *(–stk)  = (uint32)0x08080808L;        /* R8                    */

    *(–stk)  = (uint32)0x07070707L;        /* R7                    */

    *(–stk)  = (uint32)0x06060606L;        /* R6                    */

    *(–stk)  = (uint32)0x05050505L;        /* R5                    */

    *(–stk)  = (uint32)0x04040404L;        /* R4                    */

    return (stk);

} /*

 * 创建新的任务

*/

TCB*  OSTaskCreate(void* task, OS_STK *stack,PRIO_TYPE prio)

{

 TCB *pTCB;

 OS_CPU_SR  cpu_sr = 0;

 

 OS_ENTER_CRITICAL();

 

 pTCB = OSGetFreeTCB(prio);

 if (NULL == pTCB)

 {

  OS_EXIT_CRITICAL();

  return NULL;

 }

 pTCB->pStackTop = OSTaskStkInit(task, stack);

 pTCB->CurPriority = prio;

 pTCB->TCBDelay = 0;

 

 TaskNUM++;

 OS_EXIT_CRITICAL();

 return pTCB;

}   创建新任务函数很简单,只要懂了OSGetFreeTCB(prio);这个函数就没啥问题。创建的任务,是一个有序的表,就是一个存储元素为   TCB类型的数组TCB  OSTCBTable[MAX_TASK_NUM];在这个数组中,先在OSTCBTable[0]中创建一个任务,如果再创建一个任务,这个任务比上个任务的优先级高,那么OSTCBTable[0]中存储优先级高的任务,那个之前先创建的低优先级的任务搬移到OSTCBTable[1]中。如果再创建一个任务,任务优先级会与前两个任务对比,若比前两个都低,就放在第三个位置OSTCBTable[2]中,否则就重新排序,总之,使OSTCBTable数组中的任务始终是按优先级从高到低的顺序排列。   以下是这种思想实现的OSGetFreeTCB(prio)函数: /*在TCB表中取一个空闲的节点,给新任务使用*/

/*对OSTCBTable表这个有序表进行插入排序*/

/*将优先级高的任务放在前面*/

TCB* OSGetFreeTCB(PRIO_TYPE prio)

{

 TCB *pTCB;

 int32 index=0,orgIndex;

 pTCB = &(OSTCBTable[0]);

 for (index = 0;index < TaskNUM+1;index++)

 {

  pTCB = OSTCBTable+index;

  /*已经是空闲TCB了,就直接使用*/

  if (NULL == pTCB->pStackTop)

  {

   return (TCB*)pTCB;

  }

  /*若新任务优先级比较低或相等,则向后继续找*/

  if (pTCB->CurPriority >= prio)

  {

   continue;

  }

  else /*pTCB->CurPriority < prio 找到了该插入的位置了*/

  {

   /*保存当前位置*/

   orgIndex = index;

   /*从当前节点遍历到最后一个使用了的节点*/

   for( index = TaskNUM ; index > orgIndex ; index– )

   {

    pTCB = OSTCBTable+index;

    /*将前一个节点的数据,保存到当前节点*/

    _mem_copy((uint8 *)(pTCB),(uint8 *)(pTCB-1),sizeof(TCB));

   }

   _mem_clr((uint8 *)(pTCB-1),sizeof(TCB))  ;

  

   return (TCB*)(pTCB-1);

  }

 }

 return (TCB*)NULL;

}
  任务创建是基于一个有序表。这种方法虽然简单直观,但也有很大缺点。比如如果建立了100个任务,又准备建立第101个任务,且第101个任务优先级是最高的。那么创建任务就很慢,需要前面一百个任务依次都向后移,第101个任务放在数组的最前面。这是十分耗时的操作,随着任务数的增加,创建任务的时间也随着增加。后面还要讲到,这种方法建立的任务查找最高优先级时,需要遍历数组,在效率上也是不快的,尤其是任务数目多时。任务创建之后,接下来的事情就是何时需要任务切换,如何查找最高优先级了。先说下何时需要任务切换,每个任务都是一个while(1)死循环,在里面执行到OSTimedly()时就会挂起当前任务,查找最高优先级的任务。每个任务的控制块中都有个延时时间的变量,当这个延时时间变量大于0,说明这个任务还处于睡眠或挂起状态,不能够被执行。因此还需要一个系统时钟函数,作为整个系统的调度中心,每到一个系统时钟中断,让所有任务的延时时间减一。 /*

 *系统时钟函数,在时钟中断中调用

*/

void  OSTimeTick (void)

{

    int8 index;

    TCB  *pTCB;

    uint8 flagFirstTask=0;

    OS_CPU_SR  cpu_sr = 0;

    OS_ENTER_CRITICAL();

    /*初始化*/

    OSNewTCB = NULL;

 /*禁止调度*/

    if (OSScheLock != 0)

    {

       OS_EXIT_CRITICAL();

       return;

    }

 for (index = 0;index < TaskNUM;index++)

 {

     pTCB = OSTCBTable+index;

    /*该任务在睡眠状态,必须将所有时延都–*/

     if (pTCB->TCBDelay > 0)

     {

        pTCB->TCBDelay–;

        continue;

     }

     /*该任务被挂起*/

     if (pTCB->TaskStat == OS_Task_Pend)

     {

         continue;

     }

    /*任务优先级查找算法,以后考虑改进查找速度*/

   /* 是否找到了应该调度进去的就绪任务*/

  if (flagFirstTask==0)

  {

   /*找到了最高优先级的任务,

     并且比当前任务优先级高*/

     if (OSCurTCB->CurPriority < pTCB->CurPriority)

     {

        flagFirstTask = 1;

        OSNewTCB = pTCB;

        continue;

     }

    /*找到了比当前优先级低的任务*/

    if (OSCurTCB->CurPriority > pTCB->CurPriority)

   {

       if (OSNewTCB == NULL)

      {

          flagFirstTask = 1;

          OSNewTCB = pTCB;

          continue  ;

      }

      else

     {

         flagFirstTask = 1;

        continue  ;

      }

   }

 

   /*找到了最高优先级的任务,

     并且跟当前任务优先级相等*/

   if (OSCurTCB->CurPriority == pTCB->CurPriority)

   {

    /*该任务在当前任务之后*/

    if ((pTCB > OSCurTCB)||(pTCB == OSCurTCB))

    {

     flagFirstTask = 1;

     OSNewTCB = pTCB;

     continue  ;

    }

    /*在当前任务之前或者就是当前任务

      则还需要继续向后搜索第一个同优先级的任务*/

    if ((pTCB < OSCurTCB)&&(OSNewTCB == NULL))

    {

      OSNewTCB = pTCB;

      continue;

    }

   }

   continue;

  }

 

 }

 OS_EXIT_CRITICAL();

}    在时钟中断里,需要调用这个函数,这个函数的作用很简单,一方面让每个任务的延时时间减一,一方面查找最高优先级的任务,找到了最高优先级的任务时,就把 OSNewTCB = pTCB;把最高优先级的任务堆栈指针赋给 OSNewTCB 。 void SysTick_Handler(void)

{

 OSIntEnter();  //进入中断

 OSTimeTick();

 OSIntExit();        //触发任务切换软中断

} /*

*记录中断嵌套层数

*/

void  OSIntEnter (void)

{

    if (NULL != OSCurTCB)

   {

        if (OSIntNesting < 255u)

        {

            OSIntNesting++;                      /* Increment ISR nesting level                        */

        }

    }

} /*

*中断退出时调用,触发中断级任务切换

*/

void  OSIntExit (void)

{

     OS_CPU_SR  cpu_sr = 0u;

    if (NULL!= OSCurTCB)

    {

        OS_ENTER_CRITICAL();

        if (OSIntNesting > 0u)

        {                         

            OSIntNesting–;

        }

        if (OSIntNesting == 0u)

        {                        

            /* 当所有的中断完成候再判断是否调度  */

            if (OSNewTCB != OSCurTCB)

            {

              /* 中断级任务切换  */

              OSIntCtxSw();  

            }

        }

        OS_EXIT_CRITICAL();

    }

}
  在时钟中断里,退出时调用 OSIntExit();        //触发任务切换软中断   在这个函数中,比较当前任务指针是否发生了改变,若发生了改变,说明需要进行任务切换了。   下面再看看延时函数OSTimeDly (int32 ticks) void  OSTimeDly (int32 ticks)

{

    OS_CPU_SR  cpu_sr = 0;

    int8   index;

    TCB    *pTCB;

 

    OS_ENTER_CRITICAL();

    OSCurTCB->TCBDelay = ticks;

    OSNewTCB = NULL;

   /*任务优先级查找算法,从当前任务

    向后遍历,第一个最大的优先级的任务

    就是需要调度进去的任务*/

    for (index = 0; index < TaskNUM;index++)

    {

       pTCB = OSTCBTable+index;

       /*跳过睡眠任务*/

       if (pTCB->TCBDelay != 0)

       {

           continue;

       }

       /*跳过挂起任务*/

       if  (pTCB->TaskStat == OS_Task_Pend)

       {  

            continue;

       }

      /*找到了最高优先级的任务,

       并且比当前任务优先级高*/

      if (OSCurTCB->CurPriority < pTCB->CurPriority)

     {

         OSNewTCB = pTCB;

         break;

      }

      /*找到了比当前优先级低的任务*/

     if (OSCurTCB->CurPriority > pTCB->CurPriority)

     {

         /*如果当前任务之前有同优先级的就绪任务,

         则选择该任务,否则就使用*/

        if (OSNewTCB == NULL)

        {

            OSNewTCB = pTCB;

        }      

        break;

  }

 

  /*找到了最高优先级的任务,

    并且跟当前任务优先级相等*/

  if (OSCurTCB->CurPriority == pTCB->CurPriority)

  {

     /*该任务在当前任务之后*/

     if ((pTCB > OSCurTCB))

     {

          OSNewTCB = pTCB;

          break  ;

     }

   /*在当前任务之前或者就是当前任务

     则还需要继续向后搜索第一个同优先级的任务*/

    if (((pTCB < OSCurTCB)||(pTCB == OSCurTCB))

        &&(OSNewTCB == NULL))

    {

         OSNewTCB = pTCB;

         continue;

    }

  }

 

 }

 OS_EXIT_CRITICAL();

 OSTaskSche();

}   延时函数也很简单,就是一方面把需要延时的时间给任务控制结构体中的延时变量,一方面查找最高优先级的任务开始进行任务切换。以上就完成了简单的任务切换和调度。从上面可以看出,查找效率是很低的,尤其是任务数目多的时候,需要从头到尾的遍历一遍数组。创建任务和查找高优先级的任务都有改进的空间,如果以后想到更好的更有效的方法再改一改,试一试。   以上实现了简单的任务调度和切换。接下来,就是任务间如何进行同步和通讯…   在CSDN资源中可以下载工程的源码,KEILMDK4.23的IDE

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小熊分享邦(www.xxfxb.com),希望大家能坚持软件测试之路,谢谢。

赞(0) 打赏
未经允许不得转载:小熊分享邦 » 动手写简单的嵌入式操作系统一| 小熊测试

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏