0%

freertos 任务切换

FreeRTOS有两种方法触发任务切换:

  • 执行系统调用,比如普通任务可以使用taskYIELD()强制任务切换,中断服务程序中使用portYIELD_FROM_ISR()强制任务切换;
  • 系统节拍时钟中断

PendSV 中断

对于第一种任务切换方法,不管是使用taskYIELD()还是portYIELD_FROM_ISR(),最终都会执行宏portYIELD()

1
2
3
4
#define portYIELD()      do { \
// status pending sw0 mask 位置1, 与cause 协处理器中的pending位配合, 即触发sw0 上的中断
mips_biscr( SR_SINT0 ); \
} while (0)

SysTick 中断, 系统时钟节拍

对于第二种任务切换方法,在系统节拍时钟中断服务函数中,首先会更新tick计数器的值、查看是否有任务解除阻塞,如果有任务解除阻塞的话,则使能PandSV中断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 上一章中调度器创建中讲过 xPortStartScheduler 会初始化timer
__attribute__(( weak )) void vApplicationSetupTickTimerInterrupt( void )
{
/* Install interrupt handler */
// 安装tick中断处理函数 vPortTickInterruptHandler
pvPortInstallISR( HW5, vPortTickInterruptHandler );

/* Set Compare to Count as a starting point */ // 初始化compare
mips_setcompare( mips_getcount() );

/* Set the interrupt to fire 1us from now */ //systick + 1
prvSetNextTick( 1 );
}
// 来了tick中断, 会进入 vPortTickInterruptHandler
vPortTickInterruptHandler:
// 保存任务上下文 保存寄存器
portSAVE_CONTEXT
// 跳转到 vPortIncrementTick
jal vPortIncrementTick
nop
void vPortIncrementTick( void )
{
UBaseType_t uxSavedStatus;
// 暂存status寄存器, 清 status 的 8-15 的mask
uxSavedStatus = uxPortSetInterruptMaskFromISR();
{
// 任务delay到期或者任务的时间片用完了, 这个函数会返回true
if( xTaskIncrementTick() != pdFALSE )
{
// 触发pendsv 中断, 因为前面清了mask,因此这里只有sw0的mask置位了
portYIELD();
}
}
// 恢复status寄存器
vPortClearInterruptMaskFromISR( uxSavedStatus );

/* Look for the ISR stack getting near or past its limit. */
portCHECK_ISR_STACK();

/* Clear timer interrupt. */ // 清除timer cause hw5 pending位
configCLEAR_TICK_TIMER_INTERRUPT();
}

xTaskIncrementTick

tick中断到的时候, 什么条件下要触发pendsv中断的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
// 调度器未被挂起
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
// 当前tick 计时数更新
const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;

/* Increment the RTOS tick, switching the delayed and overflowed
delayed lists if it wraps to 0. */
xTickCount = xConstTickCount;
if( xConstTickCount == ( TickType_t ) 0U )
{
// tick 溢出, 切换delay list
taskSWITCH_DELAYED_LISTS();
}
// 当前tick 大于 下一个要解除阻塞的任务的timeout
if( xConstTickCount >= xNextTaskUnblockTime )
{
// 这里为什么用循环? 因为pxDelayedTaskList 里可能不止一个任务在此刻到期了, 如果有多个任务到期, 就要把这几个任务挨个加入到就绪列表中进行排队等待调度.
for( ;; )
{
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
// delay list为空, 设置xNextTaskUnblockTime 为UINT_MAX
xNextTaskUnblockTime = portMAX_DELAY;
break;
}
else
{
//这个队列是按照TCB的timeout 升序排列的,排在前面的是timeout小的
pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
// 从TCB的 xStateListItem 取出为其设置的timeout
xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );

if( xConstTickCount < xItemValue )
{
// 还没到时间解除这个任务的阻塞状态呢, 直接退出循环, 并把xNextTaskUnblockTime 更新为这个排在最前面timeout最小的任务的timeout
xNextTaskUnblockTime = xItemValue;
break;
}
// 能来到这里说明 xConstTickCount > xItemValue, 说明到时间解除排在delaylist最前面的timeout最小的任务了, 将这个任务从delaylist中删除, 出队.
( void ) uxListRemove( &( pxTCB->xStateListItem ) );

/* Is the task waiting on an event also? If so remove
it from the event list. */
// 这个任务在等待事件吗? 如果正在等事件唤醒的话, 将其从它所属的事件列表中抹掉.
// 这个地方怎么理解? 比如任务要take一个信号量, 并为其设置了timeout, 所以timeout时间到了, 还没take到, 这时要把这个任务唤醒解除阻塞状态了, 同时还要把其从等待队列中抹掉. 因为这个任务不用再等这个信号量了, 可以解除阻塞了.
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
// 任务切换到就绪状态, 就是还要排队, 如果它前面没有别的任务了, 就要调度到这个任务. 如果它前面有别的任务,那忍着,先让排在它前面的任务先得到调度. 调度就是拿到cpu资源的意思
prvAddTaskToReadyList( pxTCB );
// 只有配置了configUSE_PREEMPTION的情况下, 且这个任务的优先级大于等于当前正在运行的任务的优先级, 才会调度到这个任务, 不然就等着当前任务运行完, 切换到非就绪状态
#if ( configUSE_PREEMPTION == 1 )
{
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xSwitchRequired = pdTRUE;
}
}
#endif /* configUSE_PREEMPTION */
}
}
}

// 配置了 configUSE_PREEMPTION 和 configUSE_TIME_SLICING的情况下, 相同优先级的任务时间片轮转, 自己的时间片到期了,就切到和它优先级一致的其他任务上, 前提是其他任务已经排到就绪列表中了, 这个比较挫,不支持设置任务要用多少个时间片? rtthread是支持的
#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
{
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
{
xSwitchRequired = pdTRUE;
}
}
#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */

#if ( configUSE_TICK_HOOK == 1 )
{
// 配置了 configUSE_TICK_HOOK, 每个tick 在调度器未被挂起的情况下会调用vApplicationTickHook函数,这个函数里只能加入简单的功能,不能阻塞tick 函数的运行, 需要运算的时间比较短
if( uxPendedTicks == ( UBaseType_t ) 0U )
{
vApplicationTickHook();
}
}
#endif /* configUSE_TICK_HOOK */
}
// 调度器被挂起的情况
else
{
// 这个用来记录调度器挂起的时长
++uxPendedTicks;
#if ( configUSE_TICK_HOOK == 1 )
{
// 在调度器被挂起的情况下也会调用vApplicationTickHook函数,这个函数里只能加入简单的功能,不能阻塞tick 函数的运行, 需要运算的时间比较短
vApplicationTickHook();
}
#endif
}

#if ( configUSE_PREEMPTION == 1 )
{
// 这个是resume 调度器的时候, 设置的标记, 说明有任务被pending了, 要被调度
if( xYieldPending != pdFALSE )
{
xSwitchRequired = pdTRUE;
}
}
#endif /* configUSE_PREEMPTION */

return xSwitchRequired;
}

回到之前的问题, 什么条件下需要触发pendsv中断:

  1. 配置了configUSE_PREEMPTIONconfigUSE_TIME_SLICING的情况, 和当前任务优先级相同的任务要进行时间片轮转. (不能设置任务初始的时间片?)
  2. 只有配置了configUSE_PREEMPTION的情况下, 有任务到期了, 且这个任务的优先级大于等于当前正在运行的任务的优先级, 才会调度到这个任务, 不然就等着当前任务运行完, 切换到非就绪状态.
    1. 那如果没有配置configUSE_PREEMPTION, 怎样完成任务切换呢, 只有任务自己调yield, 触发pendsv中断才可以切到其他任务上. (比如调delay函数, take函数等最后都可能调到yield, 触发pendsv中断)
  3. 配置了configUSE_PREEMPTION的情况下, 调度器被挂起的时候, 有pending的任务.

PendSV 中断处理流程分析

主要有三个点:

  • 保存当前任务堆栈
  • 切换任务 (vTaskSwitchContext)
  • 切换到新任务后,将新任务的堆栈恢复
    1
    2
    3
      // 上一章中调度器创建中讲过 xPortStartScheduler 会注册SW0的中断处理函数vPortYieldISR
    /* Install the software yield handler */
    pvPortInstallISR( SW0, vPortYieldISR );
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    vPortYieldISR
    vPortYieldISR:
    portYIELD_SAVE

    jal vTaskSwitchContext
    nop

    portYIELD_RESTORE

    .end vPortYieldISR

portYIELD_SAVE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
   .macro portYIELD_SAVE
addiu sp, sp, -portCONTEXT_SIZE # sp = sp-132 这132个字节用来保存当前任务运行到此处的寄存器信息/协处理器(status cause epc)等的信息
mfc0 k1, C0_STATUS # k1 <- cp0_status
# 下面把sp 换成 pxTopofStack, 因为这个sp已经代表是当前任务TCB的pxTopofStack了
sw s6, 44(sp) # [pxTopofStack+44] <- s6
sw s5, 40(sp) # [pxTopofStack+40] <- s5
sw k1, portSTATUS_STACK_LOCATION(sp) # [pxTopofStack+128] <- cp0_status

/* Prepare to re-enable the interrupts. */
ins k1, zero, 8, 8
ori k1, k1, ( 0x80 << 8 ) # k1 的 第15位 即 hw5 置1, 这个是给timer用的号
ins k1, zero, 1, 4

/* s5 is used as the frame pointer. */
add s5, zero, sp # s5 <- sp

/* Swap to the system stack. This is not conditional on the nesting
count as this interrupt is always the lowest priority and therefore
the nesting is always 0. */
la sp, xISRStackTop # sp <- xISRStackTop !!!!'Swap to the system stack', 注意这里sp变了,叫system stack了
# 可以理解为就是给pendsv 中断上下文专门开辟的一块空间, 用来临时保存恢复一些寄存器的值, 因为寄存器不够用,需要内存来回倒腾.
lw sp, (sp)

/* Set the nesting count. */
la k0, uxInterruptNesting
addiu s6, zero, 1
sw s6, 0(k0) # uxInterruptNesting = 1

mfc0 s6, C0_EPC # s6 <- cp0_epc

/* Re-enable interrupts above configMAX_SYSCALL_INTERRUPT_PRIORITY. */
mtc0 k1, C0_STATUS # cp0_status <- k1, 这里只置了hw5, 也就是把其他的mask都干掉了, sw0 不能再响应了

/* Save the context into the space just created. s6 is saved again
here as it now contains the EPC value. */
sw ra, 120(s5)
sw s8, 116(s5)
sw t9, 112(s5)
sw t8, 108(s5)
sw t7, 104(s5)
sw t6, 100(s5)
sw t5, 96(s5)
sw t4, 92(s5)
sw t3, 88(s5)
sw t2, 84(s5)
sw t1, 80(s5)
sw t0, 76(s5)
sw a3, 72(s5)
sw a2, 68(s5)
sw a1, 64(s5)
sw a0, 60(s5)
sw v1, 56(s5)
sw v0, 52(s5)
sw s7, 48(s5)
sw s6, portEPC_STACK_LOCATION(s5) # [pxTopofStack+124] <- cp0_epc
/* s5 and s6 has already been saved. */
sw s4, 36(s5)
sw s3, 32(s5)
sw s2, 28(s5)
sw s1, 24(s5)
sw s0, 20(s5)
sw $1, 16(s5)

/* s7 is used as a scratch register as this should always be saved across
nesting interrupts. */
mfhi s7
sw s7, 12(s5) # [pxTopofStack+12] <- hi
mflo s7
sw s7, 8(s5) # [pxTopofStack+8] <- lo

/* Save the stack pointer to the task. */
la s7, pxCurrentTCB
lw s7, (s7)
sw s5, (s7) # 更新s5, 这里就是正式把任务TCB的第一项pxTopofStack更新成刚进来的sp-132的位置了

di # 全局关中断, status的第0位置0
ehb
mfc0 s7, C0_STATUS
ins s7, zero, 8, 8
ori s6, s7, ( 0x80 << 8 ) | 1 # status 重置,将8到14位清0, 只置hw5为1, 第0位 置1

/* Re-enable interrupts */
mtc0 s6, C0_STATUS # 启用中断, hw5 置位, 只响应timer
ehb

/* Clear the software interrupt in the core, so that we don't infinitely
recurse. */
mfc0 s6, C0_CAUSE
ins s6, zero, 8, 1
mtc0 s6, C0_CAUSE # cp0_cause pending位中 sw0 置0
ehb

.endm

这个函数除了status cause中断位相关的操作外, 主要的工作就是把当前运行的任务运行到此处时的寄存器信息和cp0 epc status等的信息保存到了当前任务的堆栈中, 并更新了当前任务TCB的pxTopofStack

cp0_epc什么时候被更新的,不要忘了, 这个是进的sw0的中断, 发生中断和异常一样, cp0_epc会被更新为进sw0 中断处理函数前的最后一行指令的位置

还是参考这个图
freertos 任务调度

vTaskSwitchContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void vTaskSwitchContext( void )
{
if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
{
/* The scheduler is currently suspended - do not allow a context
switch. */
// 调度器被挂起, 切换任务暂时pending
xYieldPending = pdTRUE;
}
else
{
// 调度器未被挂起, 不用pending
xYieldPending = pdFALSE;
// 检查是否堆栈溢出
taskCHECK_FOR_STACK_OVERFLOW();
taskSELECT_HIGHEST_PRIORITY_TASK(); // 这是个宏, 直接嵌进来
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
UBaseType_t uxTopPriority; \
/* Find the highest priority list that contains ready tasks. */ \
// 查找最高优先级. 就是个宏,这里直接展开了
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \
// mips_clz 从其最高位开始同最低位方向检查,直到遇到值为“1”的位,将该位之前“0”的个数保存到地址为rd的通用寄存器中,如果地址为rs的通用寄存器的所有位都为0(即0x00000000),那么将32保存到地址为rd的通用寄存器中。
// 这里巧妙的通过一个word(32个bit位), 利用位图的方式记录优先级, 那查找最高优先级就等同于从该word的最高位开始查找,找到第一个不为0的bit位即可
uxTopPriority = ( 31 - mips_clz( ( uxReadyPriorities ) ) )
// 从数组pxReadyTasksLists[uxTopPriority] 中取出任务
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
} /* taskSELECT_HIGHEST_PRIORITY_TASK() */
}
}

调度器未被挂起的情况下, 根据uxTopReadyPrioritypxReadyTasksLists[ uxTopPriority ]中找到最高优先级的任务, 赋值给**pxCurrentTCB**
这里关联到 prvAddTaskToReadyList这个函数, 任务要转为就绪状态, 才能得到调度, 而要转到就绪状态,就需要调用**prvAddTaskToReadyList**** **这个函数
这个函数做的事情:

  • 更新 uxTopReadyPriority, 调用taskRECORD_READY_PRIORITY, ( uxReadyPriorities ) |= ( 1**UL** << ( TCB->priority) ), 通过位图的方式来表示最高优先级.
  • 将TCB 的 **xStateListItem**状态列表项挂接到 pxReadyTasksLists[ TCB->priority ] 的 列表下

portYIELD_RESTORE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
   .macro portYIELD_RESTORE
mtc0 s7, C0_STATUS # 调用c的函数, s0-s7不会被使用,这是gcc的规定. 所以s7 还是调vTaskSwitchContext前的值
# 关联到上面的portYIELD_SAVE, 即 status 重置,将814位清0, 只置hw5为1, 第0位 置1
ehb

/* Restore the stack pointer from the TCB. */
la s0, pxCurrentTCB # pxCurrentTCB 已经被更新到要切换到的新任务的TCB了
lw s0, (s0)
lw s5, (s0) # s5 即 pxTopofStack

/* Restore the rest of the context. */
lw s0, 8(s5)
mtlo s0 # lo <- [pxTopofStack+8]
lw s0, 12(s5)
mthi s0 # hi <- [pxTopofStack+12]
lw $1, 16(s5)
lw s0, 20(s5)
lw s1, 24(s5)
lw s2, 28(s5)
lw s3, 32(s5)
lw s4, 36(s5)
/* s5 is loaded later. */
lw s6, 44(s5)
lw s7, 48(s5)
lw v0, 52(s5)
lw v1, 56(s5)
lw a0, 60(s5)
lw a1, 64(s5)
lw a2, 68(s5)
lw a3, 72(s5)
lw t0, 76(s5)
lw t1, 80(s5)
lw t2, 84(s5)
lw t3, 88(s5)
lw t4, 92(s5)
lw t5, 96(s5)
lw t6, 100(s5)
lw t7, 104(s5)
lw t8, 108(s5)
lw t9, 112(s5)
lw s8, 116(s5)
lw ra, 120(s5)

/* Protect access to the k registers, and others. */
di # status 第0位置0, 关中断, 保护k0 k1
ehb

/* Set nesting back to zero. As the lowest priority interrupt this
interrupt cannot have nested. */
la k0, uxInterruptNesting
sw zero, 0(k0) # uxInterruptNesting = 0

/* Switch back to use the real stack pointer. */
add sp, zero, s5 # sp <- pxTopofStack

/* Restore the real s5 value. */
lw s5, 40(sp) # s5 <- [pxTopofStack+40]

/* Pop the status and epc values. */
lw k1, portSTATUS_STACK_LOCATION(sp) # k1 <- [pxTopofStack+128]
lw k0, portEPC_STACK_LOCATION(sp) # k0 <- [pxTopofStack+124]

/* Remove stack frame. */
addiu sp, sp, portCONTEXT_SIZE # sp <- pxTopofStack + 132 !!!!

mtc0 k1, C0_STATUS # cp0_status <- [pxTopofStack+128] 这里把status恢复了, 即恢复了切换任务前中断相关的配置
mtc0 k0, C0_EPC # cp0_epc <- [pxTopofStack+124]
ehb # 指令冒险, cp0_epc与eret存在冒险,查pdf, 有人说要还要加ssnop
eret # 调度到 pxTopofStack+124 地址存的位置处执行, 下一行指令跳转到新任务的上次被切出去时的位置执行
nop

.endm

这个函数的注意点

  • 新任务的sp被更新成了pxTopofStack + 132的位置, 跟上面的流程图对应
  • sp 是什么时候从system stack切回来的
  • 当前任务运行着,突然来了pendsv中断,在进中断处理函数前, cp0_epc会变成进中断处理函数前最后一条指令的位置, 即函数运行到的位置, 这个cp0_epc被保存到了TCB->pxTopofStack+124的位置, 从别的任务切换回该任务时, 要把cp0_epc 恢复回来, 调用eret回到任务切走前的指令位置.
  • eret 和 cp0_epc的指令冒险是否还有问题,

调度器的挂起和恢复

前面接触到了很多的调度器挂起/未挂起的情况, 有必要深究一下.
先看调度器挂起和恢复的函数做了什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
void vTaskSuspendAll( void )
{
++uxSchedulerSuspended;
}

BaseType_t xTaskResumeAll( void )
{
TCB_t *pxTCB = NULL;
BaseType_t xAlreadyYielded = pdFALSE;

// 调度器挂起时,有可能ISR 将任务从等待事件列表中移出了, 这种情况下,从等待事件列表中移出的任务被加入到了xPendingReadyList中.
// 一旦调度器恢复, 此时可以安全的将xPendingReadyList中的任务挪到ready list中, 进入就绪列表排队
// 进入临界区, 关systick中断
taskENTER_CRITICAL();
{
// 这玩意可以大于1, 减到0才能恢复
--uxSchedulerSuspended;
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
// 总共创建了多少个任务, 调用xTaskCreate 创建任务, 该数+1, 调用vTaskDelete 删除任务, 该数-1
if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )
{
/* Move any readied tasks from the pending list into the
appropriate ready list. */
// 挨个将xPendingReadyList中的任务移出到readylist下
while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
{
pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) );
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
prvAddTaskToReadyList( pxTCB );
// 如果pendinglist中的任务的优先级大于等于当前任务的优先级, 将xYieldPending 置为True, 函数结束后要触发pendsv中断
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xYieldPending = pdTRUE;
}
}

if( pxTCB != NULL )
{
// 这个要结合前面的xTaskIncrementTick过程看,因为调度器被挂起时,不会更新xNextTaskUnblockTime, 如果这里不重置,在恢复调度器后,xNextTaskUnblockTime 这个时间已经不准确了.
// 因为之前xNextTaskUnblockTime表示的任务可能已经过期了, 早就过了timeout了
// 需要同时关注下任务什么时候被加入到xPendingReadyList 中的 (这个是等待事件等到了被加入进来的,可以看看相关的几个api)
// pendinglist中有任务的情况下, 要重置 xNextTaskUnblockTime, 这里直接展开了
prvResetNextTaskUnblockTime();
{
// 延时队列中没有任务, 这个直接置成UINT_MAX
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
xNextTaskUnblockTime = portMAX_DELAY;
}
else // 置成delaylist 排在最前面的任务的timeout(tick为单位)
{
( pxTCB ) = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
xNextTaskUnblockTime = listGET_LIST_ITEM_VALUE( &( ( pxTCB )->xStateListItem ) );
}
}
}
// 调度器挂起时,每来一个tick, uxPendedTicks会加1, 这里表示在调度器挂起的这段事件内, 任务多占用了uxPendedTicks这么长事件的时间片
// 因此为了任务计时准确,这里让tick更新, 不会漏掉, 同时模拟正常运行的状况,该多少个tick 切出任务就多少个tick
{
UBaseType_t uxPendedCounts = uxPendedTicks; /* Non-volatile copy. */

if( uxPendedCounts > ( UBaseType_t ) 0U )
{
do
{
if( xTaskIncrementTick() != pdFALSE )
{
xYieldPending = pdTRUE;
}
--uxPendedCounts;
} while( uxPendedCounts > ( UBaseType_t ) 0U );
// 重置tick, 下一次挂起时,这个数重新开始从0累加
uxPendedTicks = 0;
}
}
// pendinglist 中的任务优先级大于等于当前运行的任务的优先级, 且配置了configUSE_PREEMPTION的情况下要触发pendsv中断,切换任务
if( xYieldPending != pdFALSE )
{
#if( configUSE_PREEMPTION != 0 )
{
xAlreadyYielded = pdTRUE;
}
#endif
// 触发pendsv, 切换任务
taskYIELD_IF_USING_PREEMPTION();
}
}
}
}
// 退出临界区,打开systick中断
taskEXIT_CRITICAL();
return xAlreadyYielded;
}

可以看到主要的流程都在xTaskResumeAll中, 主要做了以下的事情:

  1. 处理xPendingReadyList, 在调度器挂起时,有可能任务从等待事件列表中移出了,(事件列表项, 等待的事件等到了,但这时调度器被挂起了, 因此移入了xPendingReadyList中)

这种情况下,从等待事件列表中移出的任务被加入到了xPendingReadyList中. 一旦调度器恢复, 此时可以安全的将xPendingReadyList中的任务挪到ready list中, 进入就绪列表排队
如果移入ready list中的任务的优先级大于等于当前运行任务的优先级, 在配置了configUSE_PREEMPTION的情况下,要触发pendsv中断切换任务

  1. 更新tick数, 同时模拟任务已经跑了uxPendedTicks个tick, 因为调度器挂起时, 不能因systick切换任务, 当前运行的任务会多跑uxPendedTicks的tick, 为了准确计时, 要让这个任务把多跑的这些tick吐出来

  2. xPendingReadyList中有任务时, 要重置xNextTaskUnblockTime, 因为调度器被挂起时, xNextTaskUnblockTime不再更新.

    在调度器被恢复后, 上次赋值的xNextTaskUnblockTime表示的任务可能已经过了timeout了, 所以需要重新赋值.

TODO: 需要关注下什么情况下任务会被加入到xPendingReadyList中, 看情况多数都是在调度器挂起时, 任务等待事件等到了, 被加入到的这个list?