动手写简单的嵌入式操作系统

im, 无色
im, 无色
im, 无色
884
文章
0
评论
2019年8月11日18:09:06 评论 537

      业余时间想研究一下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。

 

接下来需要完成任务间的同步和通信。

任务间同步,为什么需要任务间同步,比如对公共资源的访问,如果不同步,一个任务正在访问资源,另一个任务不知道这个资源正在被访问,也去访问了,这就出现问题了。还有就是任务再等待某一事件的触发,触发后才能运行。实现的一种同步方法就是信号量。何为信号量?举个简单的例子来说,就像是资源的标识,如停车位,当还有停车位时,车才可以停进来,但没有停车位时,外面的车就必须等待,等到有停车位时再进来。下面是一个信号量的简单实现,原理就是用一个全局变量代表可用的资源。当有资源时,这个变量加一,当这个变量为0时代表没有资源了,任务开始挂起,同时开始切换到其它任务。

/*当前信号量列表*/
OS_SEM Sem[MAX_SEM_NUM];    
/*
 * 创建信号量
*/
OS_SEM* OSSemCreate(int32 conuter)
{
     OS_CPU_SR  cpu_sr = 0;
     uint32  index;
    if (conuter < 0)
    {
       return (OS_SEM*)NULL;
    }
 
     OS_ENTER_CRITICAL();
    for(index=0;index<MAX_SEM_NUM;index++)
    { 
      if(Sem[index]==-1)
      {
         Sem[index]=conuter;
         OS_EXIT_CRITICAL();
         return(Sem[index]);
      }
    }
 
   OS_EXIT_CRITICAL();
   return (OS_SEM*)NULL;
}
int8 OSSemDelete(OS_SEM* pSem)
{
 OS_CPU_SR  cpu_sr = 0;
 OS_ENTER_CRITICAL();
 /*当且仅当信号量计数为0的时候,才能释放该信号量*/
 if ((*pSem) != 0)
 {
  OS_EXIT_CRITICAL();
  return OS_Err;
 }
 else
 {
  (*pSem) = (OS_SEM)-1;
  OS_EXIT_CRITICAL();
  return OS_OK;
 }
}
/*这个是一个不完全精确的实现*/
/*申请信号量*/
/*其超时时间不会非常精确*/
int8 OSSemPend(OS_SEM* pSem,uint32 timeout)
{
 uint32  index;
 OS_CPU_SR  cpu_sr = 0;
 for (index = 0;index < timeout;index++)
 {
  OS_ENTER_CRITICAL();
  if ((*pSem) > 0)
  {
   (*pSem)--;
   OS_EXIT_CRITICAL();
   return OS_OK;/*获取到了信号量*/
  }
  else
  {
   /*等待一个时间片*/
   OS_EXIT_CRITICAL();
   OSTimeDly(1);
  }
 }
 
 return OS_Err;
}
/*不等待,立即返回是否信号量能否获取*/
int8 OSSemGet(OS_SEM* pSem)
{
 OS_CPU_SR  cpu_sr = 0;
 OS_ENTER_CRITICAL();
 if ((*pSem) > 0)
 {
  (*pSem)--;
  OS_EXIT_CRITICAL();
  return OS_OK;/*获取到了信号量*/
 }
 OS_EXIT_CRITICAL();
 return OS_Err;
}
/*释放(发送)一个信号量*/
int8 OSSemPost(OS_SEM* pSem)
{
 OS_CPU_SR  cpu_sr = 0;
 OS_ENTER_CRITICAL();
 (*pSem)++;
 OS_EXIT_CRITICAL();
 return OS_OK;
}

信号量如何使用?如何使用信号量来进行同步?下面是一个简单的应用例子。
我们知道printf函数不可重入,在调用这个函数时,必须保证不能被其他任务占用。所以不同任务需要保持同步,当一个任务释放了信号量后另一个任务方可使用。

OS_SEM*   testSem;
void  task6(void * arg)
{
    testSem=OSSemCreate(1); //创建一个信号量
    while(1 )
    {
         OSSemPend(testSem, 0);
         printf("task 6  Running! 27\r\n");
         OSSemPost(testSem);
         OSTimeDly(100);/*100毫秒10个*/
    }
}

任务间如何通讯呢?可以用消息队列来实现。为什么要用消息队列?

消息被发送到队列中。“消息队列”是在消息的传输过程中保存消息的容器。消息队列管理器在将消息从它的源中继到它的目标时充当中间人。队列的主要目的是提供路由并保证消息的传递;如果发送消息时接收者不可用,消息队列会保留消息,直到可以成功地传它。
下面是一个简单的实现,很容易看懂

/*用于对于的标记消息队列是否使用*/
uint8 MsgQueueFlag[MAX_QUEUE_NUMBER];
/*实际的所有消息队列*/
OS_Q MsgQueue[MAX_QUEUE_NUMBER];
/*
 * 创建消息队列
*/
OS_Q* OSQCreate()
{
 OS_CPU_SR  cpu_sr = 0;
 uint32  index;
 OS_ENTER_CRITICAL();
 for(index=0;index<MAX_QUEUE_NUMBER;index++)
 { 
  /*该消息队列未被使用*/
  if (MsgQueueFlag[index]==0)
  {
   MsgQueueFlag[index]=1;
   /*该队列首尾初始化*/
   MsgQueue[index].front=NULL;
   MsgQueue[index].rear=NULL;
   OS_EXIT_CRITICAL();
   return &(MsgQueue[index]);
  }
 }
 
 OS_EXIT_CRITICAL();
 return (OS_Q*)NULL;
}
/*
*删除消息队列
*/
int8 OSQDelate(OS_Q* q)
{
 OS_CPU_SR  cpu_sr = 0;
 
 OS_ENTER_CRITICAL();
 /*信号量不存在*/
 if (q == NULL)
 {
  OS_EXIT_CRITICAL();
  return OS_Err;
 }
 /*队列指针越界*/
 if ((( q-MsgQueue ) < 0)||(( q-MsgQueue ) > (MAX_QUEUE_NUMBER-1)))
 {
  OS_EXIT_CRITICAL();
  return OS_Err;
 }
 
 /*将标记位置0*/
 MsgQueueFlag[q-MsgQueue] = (uint8)0;
 OS_EXIT_CRITICAL();
 return OS_OK;
}
/*
*发送一个消息
*该函数可用在中断函数中
*/
int8 OSQPost(OS_Q* q,OS_MSG msg)
{
    OS_CPU_SR  cpu_sr = 0;
 
   OS_ENTER_CRITICAL();
 if (q == NULL)
 {
       OS_EXIT_CRITICAL();
      return OS_Err;
 }
 if ((( q-MsgQueue ) < 0)||(( q-MsgQueue ) > (MAX_QUEUE_NUMBER-1)))
 {
      OS_EXIT_CRITICAL();
      return OS_Err;
 }
 if((q->rear+1)%MAX_MSG_NUMBER==q->front)
 { 
      OS_EXIT_CRITICAL();
      return OS_Err;
 }
 else
 {
      q->msgQueue[q->rear]=msg;
      q->rear=(q->rear+1)%MAX_MSG_NUMBER;
      OS_EXIT_CRITICAL();
      return OS_OK; 
 }
}
/*
*在有限时间片内等待一个消息
*该函数不能用在中断函数中,也不能在关中断的地方运行
*/
OS_MSG OSQPend(OS_Q *q, uint32 timeout)
{
 uint32  index;
 uint32  cpu_sr = 0;
 OS_MSG  msg;
 for (index = 0;index < timeout+1;index++)
 {
  OS_ENTER_CRITICAL();
  if (q->front==q->rear)
  { 
   OS_EXIT_CRITICAL();
   OSTimeDly(1);
  }
  else
  {
   msg=q->msgQueue[q->front];
   /*消息个数满时自动从0开始重新计数*/
   q->front=(q->front+1)%MAX_MSG_NUMBER;
   OS_EXIT_CRITICAL();
   return msg;
  }
 }
 OS_EXIT_CRITICAL();
 return NULL ;
}
 
/*
*直接获取一个消息,可用在中断函数中
*/ 
OS_MSG OSQGet(OS_Q *q)
{
 OS_MSG msg;
 uint32  cpu_sr = 0;
 OS_ENTER_CRITICAL();
 if (q->front==q->rear)
 { 
  OS_EXIT_CRITICAL();
  return NULL; 
 }
 else
 {
  msg=q->msgQueue[q->front];
  q->front=(q->front+1)%MAX_MSG_NUMBER;
  OS_EXIT_CRITICAL();
  return msg;
 }
}

消息队列使用的一个例子:

void  task6(void * arg)
{
     testQ=OSQCreate();//创建一个消息队列
    testSem=OSSemCreate(1); //创建一个信号量
 while(1 )
 {
     OSSemPend(testSem, 0);
     printf("task 6  Running! 27\r\n");
     OSSemPost(testSem);
     OSTimeDly(AppTaskDelay);/*100毫秒10个*/
 }
}
void  task3(void * arg)
{
 int i=0;
 char buf[]={1,2,3,4,5};
 while(1 )
 {
      OSSemPend(testSem, 0);
      printf("task 3  Running! 24\r\n");
      for(i=0;i<5;i++)
     {
         OSQPost(testQ,&buf[i]);//发送五个消息
         printf("send MSG %d\r\n",buf[i]);
     }
    OSSemPost(testSem);
    OSTaskSuspend(OSCurTCB);//挂起任务
    OSTimeDly(AppTaskDelay);/*100毫秒10个*/
 }
}
void  task4(void * arg)
{
    char* s;
    while(1 )
    {
          OSSemPend(testSem, 0);
          printf("task 4  Running! 25\r\n");
          s=(char*)OSQPend(testQ,0); //接收消息
          printf("recv MSG is %d\r\n",*s);
          OSSemPost(testSem);
          OSTimeDly(AppTaskDelay);/*100毫秒10个*/
    }
}

实时性和相关的优先级反转问题,
在实时领域,是个很关键的问题

首先说多任务,
任务就是让一段“流程”,一般都是一遍又一遍的循环运行(死循环)。
一次“流程”运行一遍之后,常常会等待一段时间,
自己休息休息,也让其他任务也运行一下,
这就是多任务并行。

在多任务的系统之中,实时性,就是让当前最高优先级的任务优先运行;
若当前最高优先级的任务不是当前正在运行的任务,那么就要给一个时机(时钟中断),
让高优先级的任务运行,正在运行的(低优先级)任务等下再运行。
这就是实时系统中的抢占调度。

实时操作系统的本质就是,
让当前最高优先级的任务以最快的速度运行!
(如果有同优先级的任务,则大家轮流运行)

由此看来,实时的多任务设计,难度在于:
要保证系统性能满足的需求,
在硬性保证高优先级任务在deadline之前运行完的同时
也要保证低优先级的任务顺利的完成自己的工作。

当然,这里就提出了优先级反转的问题了
典型情况如下:
高优先级的任务A要请求的资源被低优先级任务C所占用,
但是任务C的优先级比任务B的优先级低
于是任务B一直运行,比A低优先级的其他任务也一直能运行,
反而高优先级的任务A不能被运行了。

从实时性上讲,若高优先级在等待一个某个资源,
那么为了保证高优先级任务能顺利运行,
则必须要让当前占用该资源的任务赶紧运行下去,直到把资源释放。
再让高优先级的任务来占用这个资源。

优先级反转在RTOS中是一个很深刻的课题,
目前还没有非常好的解决方案。
在这个问题上,目前业界比较典型的做法是VxWorks的做法
原理如下:
当任务A请求的资源被任务C所占用的时候
则将C的优先级提升到任务A的级别,让占有资源的任务先运行,
这样能在一定程度上解决优先级反转的问题。
但是这样做,事实上破坏了实时系统里面运行优先级的意义...

其他,有些商业RTOS也提出了一些解决方案
比如常见的极限优先级方案:
将使用资源的任务优先级提升到系统最高级别
使得任何使用该资源的任务都能快速通过
但是,对优先级意义的破坏性,比优先级继承方案更大!

接下来又有好多事情可以做了。比如可以细读一些其他的开源系统如ucos,freeRTOS,smallRTOS,RAW OS,keil RTX,RTTherad,uclinux,minix,linux以及一些比较著名的开源代码,虽然代码量很大,但是可以慢慢来,先看比较关注的某个模块是如何实现的。
一次看懂少部分,慢慢的就很有提高了。兴趣是最好的老师,多实践,看的再多也不如经手一遍。

转自:https://blog.csdn.net/yyz_1987/article/details/9901269

im, 无色
  • 本文由 发表于 2019年8月11日18:09:06
如何学习一门新的语言 编程开发

如何学习一门新的语言

首先要说,这并不是一篇教你如何学习的文章,因为到今天为止我也没有找到一种通用的方法来解决如何学习的问题。但是在探索的道路上,我确实产生过一些思路,我想把这些思考的过程分享出来让大家探讨。如果这对你有帮...
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: