FreeRTOS--任务(非正式)

任务创建

BaseType_t xTaskCreate(  TaskFunction_t pxTaskCode, /* 任务函数的指针 */
              const char * const pcName,    /* 任务函数名字 */
              const configSTACK_DEPTH_TYPE usStackDepth, /* 任务栈的大小,单位为字 */
              void * const pvParameters,  /* 任务函数的参数 */
              UBaseType_t uxPriority,  /* 任务的优先级 */
              TaskHandle_t * const pxCreatedTask ) /* 任务句柄,以后使用其操作任务 */
  • 调度模式
    • configUSE_PREEMPTION决定是抢占式(1)还是合作式(0)
      在这里插图片描述在这里插入图片描述

切换任务—依赖于systick定时器的中断

  • 两种情况下会触发pendSV: 1.执行某些系统调用函数(如taskYIELD); 2.SysTick中断(xPortSysTickHandler)
//整个切换的函数调用vTaskStartScheduler --> xPortStartScheduler 
//--> prvPortStartFirstTask --> vPortSVCHandler

static void prvPortStartFirstTask( void )
{
  __asm volatile(
          " ldr r0, =0xE000ED08   \n" /* 这三步将msp的值保存在r0中 */
          " ldr r0, [r0]       \n"  /* 此时r0等于0x0*/
          " ldr r0, [r0]       \n"  /* ro取到的是msp的值 */
          " msr msp, r0      \n" /*  */
          " cpsie i        \n" /* 开启全局中断 */
          " cpsie f        \n"
          " dsb          \n"
          " isb          \n"
          " svc 0          \n" /* 调用SVC中断,0对应的系统服务是vPortSVCHandler?*/
          " nop          \n"
        );
}
//将之前存在栈中的值,加载到寄存器,即恢复现场
void vPortSVCHandler( void )
{
  __asm volatile (
          "  ldr  r3, pxCurrentTCBConst2  \n" /* 加载pxCurrentTCBConst2地址到r3 */
          "  ldr r1, [r3]        \n" /* 用pxCurrentTCBConst获取pxCurrentTCB地址*/
          "  ldr r0, [r1]        \n" /* 任务控制块pxCurrentTCB的第一个成员是栈顶指针 */
          "  ldmia r0!, {r4-r11} \n" /* 将r0为基地址,依次将栈中的值存到r4-r11 */
          "  msr psp, r0         \n" /* 将现在的栈顶指针存到psp */
          "  isb                \n"
          "  mov r0, #0             \n"
          "  msr  basepri, r0          \n" /* 打开所有中断 */
          "  orr r14, #0xd          \n"    /* 为下一步做预先处理 */
          "  bx r14              \n"  /* 异常返回,自动将栈中剩余内容存到剩下的寄存器中 */
          "                  \n"
          "  .align 4            \n"   /* 这个是4字对齐 */
          "pxCurrentTCBConst2: .word pxCurrentTCB   \n" 
          /* 链接地址的别名,相当于一个指针;冒号相当于goto的直接跳转,而.word表示变量*/
        );
}


//完成上文的保存,根据优先级,切换当前任务控制块
//xPortPendSVHandler中断服务函数,定义于portable\RVDS\ARM_CM3\port.c
__asm void xPortPendSVHandler( void )//pend代表可挂起的中断
{
    extern pxCurrentTCB;    /* 定义于tasks.c的全局指针,指向正在运行的任务控制块 */
    extern vTaskSwitchContext; /* 也定义于tasks.c */

    PRESERVE8 /* 按字节对齐 */

    mrs r0, psp /* 将进程堆栈指针的值存到r0寄存器 */
    isb  /* 清洗流水线,保证前面的指令已全部执行*/

    ldr r3, =pxCurrentTCB /* 将当前任务控制块的地址存到r3 */
    ldr r2, [ r3 ]  /* 加载r3指向的内容到r2,即r2=pxCurrentTCB */

    stmdb r0 !, { r4 - r11 } /* Save the remaining registers. */
    str r0, [ r2 ] /* Save the new top of stack into the first member of the TCB. */

    stmdb sp !, { r3, r14 } 
    /* 将R3和R14临时压入堆栈,因为即将调用函数vTaskSwitchContext,
    调用函数时,返回地址自动保存到R14中,所以一旦调用发生,R14的值会被覆盖,因此需要入栈保护;
     R3保存的当前激活的任务TCB指针(pxCurrentTCB)地址,函数调用后会用到,因此也要入栈保护*/
     
    mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY 
    msr basepri, r0  /* 这一句和上一句实现关中断,通过写寄存器实现,中断屏蔽寄存器之一 */
    dsb
    isb
    bl vTaskSwitchContext /* 完成全局指针pxCurrentTCB的更新,使变量pxCurrentTCB指向新的任务来实现任务切换 */
    mov r0, #0
    msr basepri, r0  /* 这一句和上一句实现开中断 */
    ldmia sp !, { r3, r14 }

    ldr r1, [ r3 ]
    ldr r0, [ r1 ] /* The first item in pxCurrentTCB is the task top of stack. */
    ldmia r0 !, { r4 - r11 } /* Pop the registers and the critical nesting count. */
    msr psp, r0
    isb
    bx r14
    nop
}

在这里插入图片描述

空闲任务

系统在启动调度器时创建的优先级最低的任务

//启动调度器创建:
void vTaskStartScheduler( void )
{
    ...
    xReturn = xTaskCreate(  prvIdleTask,
                configIDLE_TASK_NAME,
                configMINIMAL_STACK_SIZE,
                ( void * ) NULL,
                portPRIVILEGE_BIT, 
                &xIdleTaskHandle );
}

//实际的空闲任务void prvIdleTask( void *pvParameters );也即等于下面的函数
static portTASK_FUNCTION( prvIdleTask, pvParameters )
{
  ( void ) pvParameters;
  portALLOCATE_SECURE_CONTEXT( configMINIMAL_SECURE_STACK_SIZE );
  
  for( ;; )
  {
    prvCheckTasksWaitingTermination();
    /*如果有任务删除了自身,idle线程要负责回收和删除TCB和栈*/
    #if ( configUSE_PREEMPTION == 0 )
    {
      /* 如果没有开启任务抢占,则强制进入任务调度 */
      taskYIELD();
    }
    #endif
    #if ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) )
    {
      /* 开启了任务抢占,且和IDLE线程具有相同优先级的线程不止一个,则切换任务,按时间片轮转 */
      if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) )
                                                       > ( UBaseType_t ) 1 )
      {
        taskYIELD();
      }
      else
      {
        mtCOVERAGE_TEST_MARKER();
      }
    }
    #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) */
    #if ( configUSE_IDLE_HOOK == 1 )
    {
      extern void vApplicationIdleHook( void );
      /* 空闲任务钩子函数,这个函数里的操作可以由用户自己定义,
      但不能调用任何可以阻塞空闲任务的 API 函数(要保证至少有一个任务运行)*/
      vApplicationIdleHook();
    }
    #endif
    /* tickless相关的操作,由IDLE线程调用 */
    #if ( configUSE_TICKLESS_IDLE != 0 )
    {
      TickType_t xExpectedIdleTime;
      xExpectedIdleTime = prvGetExpectedIdleTime();/* 获取等待时间 */
      if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
      { /* 大于2,即要等待超过两个tick中断时,才会去关调度,再判断进入tickless操作 */
        vTaskSuspendAll();
        {
          configASSERT( xNextTaskUnblockTime >= xTickCount );
          xExpectedIdleTime = prvGetExpectedIdleTime();
        /* 如果不想去调用portSUPPRESS_TICKS_AND_SLEEP,则下一句话会将xExpectedIdleTime置0 */
          configPRE_SUPPRESS_TICKS_AND_SLEEP_PROCESSING( xExpectedIdleTime );   
          if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
          { /* 进入tickless函数处理,Tickless的关键操作:关中断,补偿时间 */
            traceLOW_POWER_IDLE_BEGIN();
            portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
            traceLOW_POWER_IDLE_END();
          }
          else
          {
            mtCOVERAGE_TEST_MARKER();
          } 
          ( void ) xTaskResumeAll();  
    }/* end configUSE_TICKLESS_IDLE*/
  }/* end for*/
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值