freeRTOS 开启关闭调度器、挂起恢复调度器、vTaskStepTick

1. 开启调度器
vTaskStartScheduler
| + vPortSetupTimerInterrupt 设置systick,初始化低功耗运行系统补偿时间
+----xPortStartScheduler -- + prvEnableVFP 开启浮点运算单元
+ prvStartFirstTask 开启第一个任务,SVC异常处理函数
2. 关闭调度器,啥都没用。

3. 调度器挂起,挂起层数计数变量。
4. 调度器恢复

5. 低功耗模式系统补偿函数

 

 

 

 

开启调度器

  1 void vTaskStartScheduler( void )
  2 {
  3 BaseType_t xReturn;
  4 
  5     
  6     /* Add the idle task at the lowest priority. */
  7     #if( configSUPPORT_STATIC_ALLOCATION == 1 )  静态添加空闲任务
  8     {
  9         StaticTask_t *pxIdleTaskTCBBuffer = NULL;
 10         StackType_t *pxIdleTaskStackBuffer = NULL;
 11         uint32_t ulIdleTaskStackSize;
 12 
 13         /* The Idle task is created using user provided RAM - obtain the
 14         address of the RAM then create the idle task. */  添加Idle任务
 15         vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );
 16         xIdleTaskHandle = xTaskCreateStatic(    prvIdleTask,
 17                                                 "IDLE",
 18                                                 ulIdleTaskStackSize,
 19                                                 ( void * ) NULL,
 20                                                 ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
 21                                                 pxIdleTaskStackBuffer,
 22                                                 pxIdleTaskTCBBuffer ); /*lint !e961 */
 23 
 24         if( xIdleTaskHandle != NULL )
 25         {
 26             xReturn = pdPASS;
 27         }
 28         else
 29         {
 30             xReturn = pdFAIL;
 31         }
 32     }
 33     #else
 34     {
 35         /* The Idle task is being created using dynamically allocated RAM. */
 36         xReturn = xTaskCreate(    prvIdleTask,
 37                                 "IDLE", configMINIMAL_STACK_SIZE,
 38                                 ( void * ) NULL,
 39                                 ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
 40                                 &xIdleTaskHandle ); /*lint !e961 */
 41     }
 42     #endif /* configSUPPORT_STATIC_ALLOCATION */
 43 
 44     #if ( configUSE_TIMERS == 1 )
 45     {
 46         if( xReturn == pdPASS )
 47         {
 48             xReturn = xTimerCreateTimerTask();  创建定时器服务!后面分析
 49         }
 50         else
 51         {
 52             mtCOVERAGE_TEST_MARKER();
 53         }
 54     }
 55     #endif /* configUSE_TIMERS */
 56 
 57     if( xReturn == pdPASS )
 58     {
 59         /* Interrupts are turned off here, to ensure a tick does not occur
 60         before or during the call to xPortStartScheduler().  The stacks of
 61         the created tasks contain a status word with interrupts switched on
 62         so interrupts will automatically get re-enabled when the first task
 63         starts to run. */
 64         portDISABLE_INTERRUPTS();
 65 
 66         #if ( configUSE_NEWLIB_REENTRANT == 1 )  【略】
 67         {
 68             /* Switch Newlib's _impure_ptr variable to point to the _reent
 69             structure specific to the task that will run first. */
 70             _impure_ptr = &( pxCurrentTCB->xNewLib_reent );
 71         }
 72         #endif /* configUSE_NEWLIB_REENTRANT */
 73 
 74         xNextTaskUnblockTime = portMAX_DELAY;   三个值的初始化
 75         xSchedulerRunning = pdTRUE;
 76         xTickCount = ( TickType_t ) 0U;
 77 
 78         /* If configGENERATE_RUN_TIME_STATS is defined then the following
 79         macro must be defined to configure the timer/counter used to generate
 80         the run time counter time base. */
 81         portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();  时间统计功能若打开,用户实现此宏。
 82 
 83         /* Setting up the timer tick is hardware specific and thus in the
 84         portable interface. */
 85         if( xPortStartScheduler() != pdFALSE )
 86         {
 87             /* Should not reach here as if the scheduler is running the  
 88             function will not return. */  as(因为)
 89         }
 90         else
 91         {
 92             /* Should only reach here if a task calls xTaskEndScheduler(). */  除非(if)
 93         }
 94     }
 95     else
 96     {
 97         /* This line will only be reached if the kernel could not be started,
 98         because there was not enough FreeRTOS heap to create the idle task
 99         or the timer task. */
100         configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );  idle task 或 timer task 没创建成功
101     }
102 
103     /* Prevent compiler warnings if INCLUDE_xTaskGetIdleTaskHandle is set to 0,
104     meaning xIdleTaskHandle is not used anywhere else. */
105     ( void ) xIdleTaskHandle;  防止编译器报错,xTaskGetIdleTaskHandle为0时,编译器提示idle task not use.
106 }
107 /*-----------------------------------------------------------*/
   硬件初始化:systick、FPU单元、PendSV中断。
1
BaseType_t xPortStartScheduler( void ) 2 { 3 /* configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to 0. 4 See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html */ 5 configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY ); 6 7 /* This port can be used on all revisions of the Cortex-M7 core other than 8 the r0p1 parts. r0p1 parts should use the port from the 9 /source/portable/GCC/ARM_CM7/r0p1 directory. */ 10 configASSERT( portCPUID != portCORTEX_M7_r0p1_ID ); 11 configASSERT( portCPUID != portCORTEX_M7_r0p0_ID ); 12 13 #if( configASSERT_DEFINED == 1 ) 【略】 14 { 15 volatile uint32_t ulOriginalPriority; 16 volatile uint8_t * const pucFirstUserPriorityRegister = ( uint8_t * ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER ); 17 volatile uint8_t ucMaxPriorityValue; 18 19 /* Determine the maximum priority from which ISR safe FreeRTOS API 20 functions can be called. ISR safe functions are those that end in 21 "FromISR". FreeRTOS maintains separate thread and ISR API functions to 22 ensure interrupt entry is as fast and simple as possible. 23 24 Save the interrupt priority value that is about to be clobbered. */ 25 ulOriginalPriority = *pucFirstUserPriorityRegister; 26 27 /* Determine the number of priority bits available. First write to all 28 possible bits. */ 29 *pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE; 30 31 /* Read the value back to see how many bits stuck. */ 32 ucMaxPriorityValue = *pucFirstUserPriorityRegister; 33 34 /* The kernel interrupt priority should be set to the lowest 35 priority. */ 36 configASSERT( ucMaxPriorityValue == ( configKERNEL_INTERRUPT_PRIORITY & ucMaxPriorityValue ) ); 37 38 /* Use the same mask on the maximum system call priority. */ 39 ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue; 40 41 /* Calculate the maximum acceptable priority group value for the number 42 of bits read back. */ 43 ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS; 44 while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE ) 45 { 46 ulMaxPRIGROUPValue--; 47 ucMaxPriorityValue <<= ( uint8_t ) 0x01; 48 } 49 50 /* Shift the priority group value back to its position within the AIRCR 51 register. */ 52 ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT; 53 ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK; 54 55 /* Restore the clobbered interrupt priority register to its original 56 value. */ 57 *pucFirstUserPriorityRegister = ulOriginalPriority; 58 } 59 #endif /* conifgASSERT_DEFINED */ 60 61 /* Make PendSV and SysTick the lowest priority interrupts. */ 62 portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI; 63 portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI; 64 65 /* Start the timer that generates the tick ISR. Interrupts are disabled 66 here already. */ 67 vPortSetupTimerInterrupt(); 68 69 /* Initialise the critical nesting count ready for the first task. */ 70 uxCriticalNesting = 0; 临界区嵌套计数器,防止多个临界区时,其中一个临界区退出,导致多个临界区都退出。 71 72 /* Ensure the VFP is enabled - it should be anyway. */ 73 prvEnableVFP(); 使能VFP 74 75 /* Lazy save always. */ 76 *( portFPCCR ) |= portASPEN_AND_LSPEN_BITS; 使S0~S15和FPSCR寄存器,中断时自动保存和恢复。FPCCR寄存器、惰性压栈,参见《权威指南》12章 浮点运算 77 78 /* Start the first task. */ 79 prvStartFirstTask(); 80 81 /* Should not get here! */ 82 return 0; 83 }

一、开启systick

    void vPortSetupTimerInterrupt( void )
    {
        /* Calculate the constants required to configure the tick interrupt. */
        #if configUSE_TICKLESS_IDLE == 1
        {
            ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );  系统时钟频率/sysTick频率
            xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;  //0xFFF_FFF ul
            ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ );
        }
        #endif /* configUSE_TICKLESS_IDLE */

        /* Configure SysTick to interrupt at the requested rate. */
        portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;                                  设置重装载值
        portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );  开启xx
    }
/* A fiddle欺骗的 factor to estimate the number of SysTick counts that would have
occurred while the SysTick counter is stopped during tickless idle
calculations. */
#define portMISSED_COUNTS_FACTOR    ( 45UL )   详见低功耗运行补偿时间
portNVIC_SYSTICK_CLK_BIT         1<<2
portNVIC_SYSTICK_INT_BIT         1<<1
portNVIC_SYSTICK_ENABLE_BIT      1<<0

#define portNVIC_SYSTICK_CTRL_REG            ( * ( ( volatile uint32_t * ) 0xe000e010 ) )

 

 

 

二、开启FPU,浮点处理单元

__asm void prvEnableVFP( void )
{
  PRESERVE8
  ldr.w r0, =0xE000ED88        ;R0=0XE000ED88  SCB->CPACR寄存器  Coprocessor Access Contrl
  ldr r1, [r0]                 ;从 R0 的地址读取数据赋给 R1
  orr r1, r1, #( 0xf << 20 )   ;R1=R1|(0xf<<20)  bit20~bit23,开启FPU
  str r1, [r0]                 ;R1 中的值写入 R0 保存的地址中
  bx r14
  nop
}

 CPACR Register to enable floating point unit feature; available on Cortex -M4 with floating point unit only

 

 

三、开启第一个任务

__asm void prvStartFirstTask( void )
{
    PRESERVE8

    /* Use the NVIC offset register to locate the stack. */
    ldr r0, =0xE000ED08
    ldr r0, [r0]         R0地址处的内容,赋值给R0
    ldr r0, [r0]         获取MSP初始值
    /* Set the msp back to the start of the stack. */
    msr msp, r0          复位MSP
    /* Globally enable interrupts. */
    cpsie i              使能中断,清除PRI MASK
    cpsie f              使能中断,清除FAULT MASK
    dsb                  数据同步屏障?
    isb                  指令同步屏障?
    /* Call SVC to start the first task. */
    svc 0                呼叫SVC异常
    nop
    nop
}

向量表重定位,向量表偏移寄存器(VTOR)。 地址就是 0XE000ED08,通过这个寄存器可以重新定义向量表。

向量表的起始地址保存的就是主栈指针MSP 的初始值。

在 FreeRTOS 中仅仅使用 SVC 异常来启动第一个任务,后面的程序中就再也用不到 SVC 了。

#define   xPortPendSVHandler   PendSV_Handler
__asm void vPortSVCHandler( void ) { PRESERVE8 /* Get the location of the current TCB. */ ldr r3, =pxCurrentTCB 获取pxCurrentTCB的存储地址 ldr r1, [r3] 获取当前任务的TCB存储地址 ldr r0, [r1] TCB第一个字段就是任务堆栈的栈顶指针pxTopOfStack,获取它指向的地址 /* Pop the core registers. */ ldmia r0!, {r4-r11, r14} 多加载/存储指令(R0~R3,R12,PC,xPSR是自动压栈出栈的) msr psp, r0 更新psp任务堆栈指针 isb mov r0, #0 msr basepri, r0 BASEPRI设为0,开中断 bx r14 中断返回,执行PC指向的任务函数. }

1. pxCurrentTCB 是一个指向 TCB_t 的指针,这个指针永远指向正在运行的任务。

2. 前三步的目的就是获取要切换到的这个任务的任务栈顶指针,

因为任务所对应的寄存器值,也就是现场都保存在任务的任务堆栈中,

所以需要获取栈顶指针来恢复这些寄存器值!

3. R14 = LR寄存器,恢复时,它的内容为EXC_RETURN = 0xffff_fffd,是初始化任务堆栈时的值。

 

 

========================================================

vTaskStartScheduler 就有 vTaskEndScheduler

/**
 * task. h
 * void vTaskEndScheduler( void );
 *
 * NOTE:  At the time of writing only the x86 real mode port, which runs on a PC
 * in place of DOS, implements this function.
 *
 * Stops the real time kernel tick.  All created tasks will be automatically
 * deleted and multitasking (either preemptive or cooperative) will
 * stop.  Execution then resumes from the point where vTaskStartScheduler ()
 * was called, as if vTaskStartScheduler () had just returned.
 *
 * vTaskEndScheduler () requires an exit function to be defined within the
 * portable layer (see vPortEndScheduler () in port. c for the PC port).  This
 * performs hardware specific operations such as stopping the kernel tick.
 *
 * vTaskEndScheduler () will cause all of the resources allocated by the
 * kernel to be freed - but will not free resources allocated by application
 * tasks.
 *
 */

void vTaskEndScheduler( void )
{
    /* Stop the scheduler interrupts and call the portable scheduler end
    routine so the original ISRs can be restored if necessary. The port
    layer must ensure interrupts enable bit is left in the correct state. */
    portDISABLE_INTERRUPTS();      关中断
    xSchedulerRunning = pdFALSE;
    vPortEndScheduler();           关调度器
}

 

void vPortEndScheduler( void )
{
  /* Not implemented in ports where there is nothing to return to.
  Artificially force an assert. */
  configASSERT( uxCriticalNesting == 1000UL );  啥都没做~只有x86内核里才使用这个东东。
}

 

 

 

 

========================================================

========================================================

vTaskSuspendAll() 挂起任务调度器, 调用此函数不需要关闭可屏蔽中断。

void vTaskSuspendAll( void )
{
    /* A critical section is not required as the variable is of type
    BaseType_t.  Please read Richard Barry's reply in the following link to a
    post in the FreeRTOS support forum before reporting this as a bug! -
    http://goo.gl/wu4acr */
    ++uxSchedulerSuspended;
}

uxSchedulerSuspended 挂起嵌套计数器, 调度器挂起是支持嵌套的。

使用函数 xTaskResumeAll()可以恢复任务调度器,调用了几次 vTaskSuspendAll()挂起调度器,同样的也得调用几次 xTaskResumeAll()才会最终恢复任务调度器。

假设现在有这样一种情况, 任务 1 的优先级为 10,此时任务 1 由于等待队列TestQueue 而处于阻塞态 。

但是有段其他的 代码调用函数vTaskSuspendAll()挂起了任务调度器,

在还没有调用 xTaskResumeAll()恢复任务调度器之前,有个在外部中断发生了,在中断服务程序里面调用函数 xQueueSendFromISR()向任务 1 发送了队列 TestQueue

如果任务调度器没有阻塞的话函数 xQueueSendFromISR()会使任务 1 进入就绪态,也就是将任务 1 添加到优先级 10 对应的就绪列表 pxReadyTasksLists[10]中,这样当任务切换的时候任务 1 就会运行。

但是现在任务调度器由于函数 vTaskSuspendAll()而挂起,这个时候任务 1 就不是添加到任务就绪列表 pxReadyTasksLists[10]中了,而是添加到另一个叫做xPendingReadyList 的列表中,

xPendingReadyList 是个全局变量,在文件 tasks.c 中有定义。

当调用函数 xTaskResumeAll()恢复调度器的时候就会将挂到列表 xPendingReadyList 中的任务重新移动到它们所对应的就绪列表 pxReadyTasksLists 中。 

 

 

任务调度器恢复

  1 BaseType_t xTaskResumeAll( void )
  2 {
  3 TCB_t *pxTCB = NULL;
  4 BaseType_t xAlreadyYielded = pdFALSE;
  5 
  6     /* If uxSchedulerSuspended is zero then this function does not match a
  7     previous call to vTaskSuspendAll(). */
  8     configASSERT( uxSchedulerSuspended );
  9 
 10     /* It is possible that an ISR caused a task to be removed from an event
 11     list while the scheduler was suspended.  If this was the case then the
 12     removed task will have been added to the xPendingReadyList.  Once the
 13     scheduler has been resumed it is safe to move all the pending ready
 14     tasks from this list into their appropriate ready list. */
 15     taskENTER_CRITICAL();
 16     {
 17         --uxSchedulerSuspended;  嵌套层数变量自减一
 18 
 19         if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )    等于0,要恢复调度器了
 20         {
 21             if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )
 22             {
 23                 /* Move any readied tasks from the pending list into the
 24                 appropriate ready list. */  从PendingReadList里面,移动到ReadyList.
 25                 while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
 26                 {
 27                     pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) );
 28                     ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
 29                     ( void ) uxListRemove( &( pxTCB->xStateListItem ) );
 30                     prvAddTaskToReadyList( pxTCB );
 31 
 32                     /* If the moved task has a priority higher than the current
 33                     task then a yield must be performed. */
 34                     if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )  新出来的优先级高
 35                     {
 36                         xYieldPending = pdTRUE;  切换任务
 37                     }
 38                     else
 39                     {
 40                         mtCOVERAGE_TEST_MARKER();
 41                     }
 42                 }
 43 
 44                 if( pxTCB != NULL )
 45                 {
 46                     /* A task was unblocked while the scheduler was suspended,
 47                     which may have prevented the next unblock time from being
 48                     re-calculated, in which case re-calculate it now.  Mainly
 49                     important for low power tickless implementations, where
 50                     this can prevent an unnecessary exit from low power
 51                     state. */
 52                     prvResetNextTaskUnblockTime();  重新计算变量xNextTaskUnblockTime,详见 “删除任务 ##5”
 53                 }
 54 
 55                 /* If any ticks occurred while the scheduler was suspended then
 56                 they should be processed now.  This ensures the tick count does
 57                 not    slip逃, and that any delayed tasks are resumed at the correct
 58                 time. */
 59                 { 任务调度器挂起时,tick中断不会更新TickCnt,而是更新PendTick【见时间管理章节的TaskIncrementTick函数】
在恢复任务调度时,调用PendTick次TaskIncrementTick()函数,来恢复TickCnt!!
同时,如果有延时任务取消阻塞的动作,也会在TaskIncrementTick函数中进行。
60 UBaseType_t uxPendedCounts = uxPendedTicks; /* Non-volatile copy. */ 61 62 if( uxPendedCounts > ( UBaseType_t ) 0U ) 63 { 64 do 65 { 66 if( xTaskIncrementTick() != pdFALSE ) 67 { 68 xYieldPending = pdTRUE; 置位xYieldPending 69 } 70 else 71 { 72 mtCOVERAGE_TEST_MARKER(); 73 } 74 --uxPendedCounts; 75 } while( uxPendedCounts > ( UBaseType_t ) 0U ); 76 77 uxPendedTicks = 0; 78 } 79 else 80 { 81 mtCOVERAGE_TEST_MARKER(); 82 } 83 } 84 85 if( xYieldPending != pdFALSE ) 如果置位了 86 { 87 #if( configUSE_PREEMPTION != 0 ) 88 { 89 xAlreadyYielded = pdTRUE; 90 } 91 #endif 92 taskYIELD_IF_USING_PREEMPTION(); 置位PendSV 93 } 94 else 95 { 96 mtCOVERAGE_TEST_MARKER(); 97 } 98 } 99 } 100 else 101 { 102 mtCOVERAGE_TEST_MARKER(); 103 } 104 } 105 taskEXIT_CRITICAL(); 106 107 return xAlreadyYielded; 108 }

>1 把PendingReadyList里面的任务,恢复到ReadyList当中。(可能引起任务调度)

>2 重新计算NextTaskUnblockTime

>3 弥补在挂起调度器时,漏了的系统滴答时钟节拍。(可能引起任务调度)

 

 

 

 

========================================================

========================================================

vTaskStepTick()
此函数在使用 FreeRTOS 的低功耗 tickless 模式的时候会用到,即宏 configUSE_TICKLESS_IDLE 为 1

当使能低功耗 tickless 模式以后在执行空闲任务的时候系统时钟节拍中断就会停止运行,系统时钟中断停止运行的这段时间必须得补上,
这个工作就是由函数 vTaskStepTick()来完成的。
void vTaskStepTick( const TickType_t xTicksToJump ) {   configASSERT( ( xTickCount + xTicksToJump ) <= xNextTaskUnblockTime );   xTickCount += xTicksToJump;   traceINCREASE_TICK_COUNT( xTicksToJump ); } 函数参数 xTicksToJump 是要加上的时间值,系统节拍计数器 xTickCount 加上这个时间值得到新的系统时间。
关于 xTicksToJump 这个时间值的确定。详见“系统低功耗睡眠模式”

 

 

 

 

 

 

 

 

 

 

posted @ 2017-11-19 23:19  为民除害  阅读(4882)  评论(0编辑  收藏  举报