freeRTOS 任务切换

使用PendSV实现任务切换 

上下文切换被触发的场合可以是:
#1 执行一个系统调用
#2 系统滴答定时器(SysTick)中断。
  PendSV中断服务函数
  TaskSelectHighestPrior的两种方法

#3 最后补充了时间片调度相关的东西。

在调度器不挂起的情况下,在任务函数中,一旦置位PendSV请求,立即产生任务切换。

一旦置位,立即切换!

一旦置位,立即切换!

一旦置位,立即切换!

 

 

一、系统调用 taskYIELD:

#define taskYIELD() portYIELD()
#define portYIELD() \ { \   portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \ 往寄存器地址0xe000ed04中,写入1<<28ul.置位PendSV异常   __dsb( portSY_FULL_READ_WRITE ); \ Barriers寄存器什么的,不懂   __isb( portSY_FULL_READ_WRITE ); \ }
/*-----------------------------------------------------------*/

#define portNVIC_INT_CTRL_REG        ( * ( ( volatile uint32_t * ) 0xe000ed04 ) )
#define portNVIC_PENDSVSET_BIT        ( 1UL << 28UL )
#define portEND_SWITCHING_ISR( xSwitchRequired ) if( xSwitchRequired != pdFALSE ) portYIELD() #define portYIELD_FROM_ISR( x ) portEND_SWITCHING_ISR( x ) 中断中切换任务 /*-----------------------------------------------------------*/

二、系统滴答定时器中断:

void SysTick_Handler(void)
{
  if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
  {
    xPortSysTickHandler();
  }
  HAL_IncTick();
}


void xPortSysTickHandler( void )
{
  vPortRaiseBASEPRI();   关中断
  {
    if( xTaskIncrementTick() != pdFALSE ) //增加时钟计数器 xTickCount 的值,返回值ture时需要切换任务。函数TaskIncrementTick见章节:时间管理
    {
      portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; 置位PendSV
    }
  }
  vPortClearBASEPRIFromISR();  开中断
}
关中断、开中断
static
portFORCE_INLINE void vPortRaiseBASEPRI( void ) { uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; __asm { /* Set BASEPRI to the max syscall priority to effect a critical BASEPRI设为Max_syscall,实际上产生了临界段 section. */ msr basepri, ulNewBASEPRI dsb isb } } /*-----------------------------------------------------------*/ static portFORCE_INLINE void vPortClearBASEPRIFromISR( void ) { __asm { /* Set BASEPRI to 0 so no interrupts are masked. This function is only used to lower the mask in an interrupt, so memory barriers are not used. */ msr basepri, #0 } } /*-----------------------------------------------------------*/

 

 

 

 

系统调用TaskYield和滴答定时器中断,都在做同一个事情。就是置位PendSV异常。

然后看一下PendSV中断服务函数:

 1 #define xPortPendSVHandler PendSV_Handler
 2 
 3 __asm void xPortPendSVHandler( void )
 4 {
 5     extern uxCriticalNesting;
 6     extern pxCurrentTCB;
 7     extern vTaskSwitchContext;
 8 
 9     PRESERVE8
10 
11     mrs r0, psp
12     isb
13     /* Get the location of the current TCB. */
14     ldr    r3, =pxCurrentTCB
15     ldr    r2, [r3]
16 
17     /* Is the task using the FPU context?  If so, push high vfp registers. */
18     tst r14, #0x10
19     it eq
20     vstmdbeq r0!, {s16-s31}
21 
22     /* Save the core registers. */
23     stmdb r0!, {r4-r11, r14}
24 
25     /* Save the new top of stack into the first member of the TCB. */
26     str r0, [r2]
27 
28     stmdb sp!, {r3}
29     mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
30     msr basepri, r0
31     dsb
32     isb
33     bl vTaskSwitchContext  任务调度函数34     mov r0, #0
35     msr basepri, r0
36     ldmia sp!, {r3}
37 
38     /* The first item in pxCurrentTCB is the task top of stack. */
39     ldr r1, [r3]
40     ldr r0, [r1]
41 
42     /* Pop the core registers. */
43     ldmia r0!, {r4-r11, r14}
44 
45     /* Is the task using the FPU context?  If so, pop the high vfp registers
46     too. */
47     tst r14, #0x10
48     it eq
49     vldmiaeq r0!, {s16-s31}
50 
51     msr psp, r0
52     isb
53     #ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata */
54         #if WORKAROUND_PMU_CM001 == 1
55             push { r14 }
56             pop { pc }
57             nop
58         #endif
59 
60     #endif
61     bx r14
62 }
 1 void vTaskSwitchContext( void )
 2 {
 3     if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
 4     {
 5         /* The scheduler is currently suspended - do not allow a context
 6         switch. */
 7         xYieldPending = pdTRUE;
 8     }
 9     else
10     {
11         xYieldPending = pdFALSE;
12         traceTASK_SWITCHED_OUT();
13 
14         #if ( configGENERATE_RUN_TIME_STATS == 1 ) 【略】
15         {
16                 #ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
17                     portALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );
18                 #else
19                     ulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();
20                 #endif
21 
22                 /* Add the amount of time the task has been running to the
23                 accumulated time so far.  The time the task started running was
24                 stored in ulTaskSwitchedInTime.  Note that there is no overflow
25                 protection here so count values are only valid until the timer
26                 overflows.  The guard against negative values is to protect
27                 against suspect run time stat counter implementations - which
28                 are provided by the application, not the kernel. */
29                 if( ulTotalRunTime > ulTaskSwitchedInTime )
30                 {
31                     pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime );
32                 }
33                 else
34                 {
35                     mtCOVERAGE_TEST_MARKER();
36                 }
37                 ulTaskSwitchedInTime = ulTotalRunTime;
38         }
39         #endif /* configGENERATE_RUN_TIME_STATS */
40 
41         /* Check for stack overflow, if configured. */
42         taskCHECK_FOR_STACK_OVERFLOW();
43 
44         /* Select a new task to run using either the generic C or port
45         optimised asm code. */
46         taskSELECT_HIGHEST_PRIORITY_TASK();
47         traceTASK_SWITCHED_IN();
48 
49         #if ( configUSE_NEWLIB_REENTRANT == 1 )【略】
50         {
51             /* Switch Newlib's _impure_ptr variable to point to the _reent
52             structure specific to this task. */
53             _impure_ptr = &( pxCurrentTCB->xNewLib_reent );
54         }
55         #endif /* configUSE_NEWLIB_REENTRANT */
56     }
57 }

TaskSelectHighestPrior的方法有两种:

方法1:通用的c语言方法
    #define taskSELECT_HIGHEST_PRIORITY_TASK()                                                            \
    {                                                                                                    \
    UBaseType_t uxTopPriority = uxTopReadyPriority;                                                        \
                                                                                                        \
        /* Find the highest priority queue that contains ready tasks. */                                \
        while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) )                            \
        {                                                                                                \
            configASSERT( uxTopPriority );                                                                \
            --uxTopPriority;                                                                            \
        }                                                                                                \
                                                                                                        \
        /* listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of                        \
        the    same priority get an equal share of the processor time. */                                    \
        listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );            \
        uxTopReadyPriority = uxTopPriority;                                                                \
    } /* taskSELECT_HIGHEST_PRIORITY_TASK */

(1)uxTopReadyPriority 代表处于就绪态的最高优先级值,

每次创建任务时都会判断新任务的优先级是否大于 uxTopReadyPriority,如果大于的话就将这个新任务的优先级赋值给变量 uxTopReadyPriority。

函数 prvAddTaskToReadyList() 也会修改这个值,将某个任务添加到就绪列表中的时候,用 uxTopReadyPriority 来记录就绪列表中的最高优先级。

这里就从这个最高优先级开始判断,看看哪个列表不为空就说明哪个优先级有就绪的任务。

函数 listLIST_IS_EMPTY()用于判断某个列表是否为空,

uxTopPriority 用来记录这个有就绪任务的优先级。(临时值)

(2)已经找到了有就绪任务的优先级了, 接下来就是从对应的列表中找出下一个要运行的任务,

查找方法就是使用函数 listGET_OWNER_OF_NEXT_ENTRY()来获取列表中的下一个列表项,

然后将获取到的列表项所对应的任务控制块赋值给 pxCurrentTCB,这样我们就确定了下一个要运行的任务了。


可以看出通用方法是完全通过 C 语言来实现的,肯定适用于不同的芯片和平台, 而且对于任务数量没有限制, 但是效率肯定相对于使用硬件方法的要低很多。

 1 方法2:硬件实现
 2     #define taskSELECT_HIGHEST_PRIORITY_TASK()                                                        \
 3     {                                                                                                \
 4     UBaseType_t uxTopPriority;                                                                        \
 5                                                                                                     \
 6         /* Find the highest priority list that contains ready tasks. */                                \
 7         portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );                                \
 8         configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 );        \
 9         listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );        \
10     }
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )

使用硬件方法的时候 uxTopReadyPriority 就不代表处于就绪态的最高优先级了,而是使用每个 bit 代表一个优先级, bit0 代表优先级 0, bit31 就代表优先级 31,

当某个优先级有就绪任务的话就将其对应的 bit 置 1。

从这里就可以看出,如果使用硬件方法的话最多只能有 32 个优先级。

__clz(uxReadyPriorities)就是计算 uxReadyPriorities 的前导零个数(汇编指令CLZ)

得到 uxTopReadyPriority 的前导零个数以后在用 31 减去这个前导零个数,得到的就是处于就绪态的最高优先级了,

比如优先级 30 为此时的处于就绪态的最高优先级, 30 的前导零个数为1,那么 31-1=30,得到处于就绪态的最高优先级为 30。


可以看出硬件方法借助一个指令就可以快速的获取处于就绪态的最高优先级,但是会限制任务的优先级数。

 

 

 

 

三、最后说一点时间片调度相关的东西: 

FreeRTOS 支持多个任务同时拥有一个优先级, 一个任务运行一个时间片(一个时钟节拍的长度)后让出 CPU 的使用权,让拥有同优先级的下一个任务运行。

时间片的长度由宏 configTICK_RATE_HZ 来确定,一个时间片的长度就是滴答定时器的中断周期,比如configTICK_RATE_HZ 为 1000,那么一个时间片的长度就是 1ms。

SysTick_Handler  ->  xPortSysTickHandler  ->  xTaskIncrementTick如下:

  1 BaseType_t xTaskIncrementTick( void )
  2 {
  3 TCB_t * pxTCB;
  4 TickType_t xItemValue;
  5 BaseType_t xSwitchRequired = pdFALSE;
  6 
  7     /* Called by the portable layer each time a tick interrupt occurs.
  8     Increments the tick then checks to see if the new tick value will cause any
  9     tasks to be unblocked. */
 10     traceTASK_INCREMENT_TICK( xTickCount );
 11     if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )  调度器没挂起
 12     {


112         /* Tasks of equal priority to the currently running task will share
113         processing time (time slice) if preemption is on, and the application
114         writer has not explicitly turned time slicing off. */
115         #if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )  时间片相关的处理
116         {
117             if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
118             {
119                 xSwitchRequired = pdTRUE;
120             }
121             else
122             {
123                 mtCOVERAGE_TEST_MARKER();
124             }
125         }
126         #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */

        }
169     return xSwitchRequired;
170 }

如果当前任务所对应的优先级下有其他的任务存在,那么函数xTaskIncrementTick() 就会返回 pdTURE ,

由于函数返回值为 pdTURE ,因此函数 xPortSysTickHandler() 就会进行一次任务切换。

@@TaskIncrementTick其他的处理,见时间管理章节

 

 

 

 

 

留白

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