FreeRTOS解析

FreeRTOS源码解析

基本配置

1
#define portSTACK_GROWTH			( -1 )

任务管理

任务控制块TCB

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
/*
* Task control block. A task control block (TCB) is allocated for each task,
* and stores task state information, including a pointer to the task's context
* (the task's run time environment, including register values)
*/
typedef struct tskTaskControlBlock /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
volatile StackType_t * pxTopOfStack; /**< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */

ListItem_t xStateListItem; /**< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
ListItem_t xEventListItem; /**< Used to reference a task from an event list. */
UBaseType_t uxPriority; /**< The priority of the task. 0 is the lowest priority. */
StackType_t * pxStack; /**< Points to the start of the stack. */
char pcTaskName[ configMAX_TASK_NAME_LEN ]; /**< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */

UBaseType_t uxBasePriority; /**< The priority last assigned to the task - used by the priority inheritance mechanism. */
UBaseType_t uxMutexesHeld;

volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];

uint8_t ucDelayAborted;
} tskTCB;

/* The old tskTCB name is maintained above then typedefed to the new TCB_t name
* below to enable the use of older kernel aware debuggers. */
typedef tskTCB TCB_t;

动态创建任务

1
2
3
4
5
6
7
// tasks.c
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )

portSTACK_GROWTH < 0,栈向下增长;为避免栈增长到TCB中,需要先申请栈再申请TCB结构。

pxNewTCB->pxStack = pxStack;将栈赋值到TCB中。

prvInitialiseNewTask初始化TCB结构。

prvAddNewTaskToReadyList将任务添加到就绪列表中。

1
static void prvAddNewTaskToReadyList( TCB_t * pxNewTCB )

taskENTER_CRITICAL进入临界区,关闭中断。

prvAddTaskToReadyList将任务按优先级加入就绪队列中。

taskEXIT_CRITICAL退出临界区,开中断。

开始任务调度

1
2
// tasks.c
void vTaskStartScheduler( void )

xTaskCreate创建idle任务。

xTimerCreateTimerTask创建计时器任务。

portDISABLE_INTERRUPTS禁用中断。

xPortStartScheduler调用开启硬件调度接口,一般是启动tick计时器。

1
2
// port.c
BaseType_t xPortStartScheduler( void )

portDISABLE_INTERRUPTS禁用中断。

configSETUP_TICK_INTERRUPT配置tick计时器。

vPortRestoreTaskContext开始执行第一个任务。

汇编程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// portASM.S
/******************************************************************************
* vPortRestoreTaskContext is used to start the scheduler.
*****************************************************************************/
.align 8
.type vPortRestoreTaskContext, %function
vPortRestoreTaskContext:
.set freertos_vector_base, _freertos_vector_table

/* Install the FreeRTOS interrupt handlers. */
LDR X1, =freertos_vector_base
#if defined( GUEST )
MSR VBAR_EL1, X1
#else
MSR VBAR_EL3, X1
#endif
DSB SY
ISB SY

/* Start the first task. */
portRESTORE_CONTEXT

任务切换

MPS2_QEMU会调用汇编处理程序xPortPendSVHandler

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
void xPortPendSVHandler( void )
{
/* This is a naked function. */

__asm volatile
(
" mrs r0, psp \n"
" isb \n"
" \n"
" ldr r3, pxCurrentTCBConst \n"/* Get the location of the current TCB. */
" ldr r2, [r3] \n"
" \n"
" stmdb r0!, {r4-r11} \n"/* Save the remaining registers. */
" str r0, [r2] \n"/* Save the new top of stack into the first member of the TCB. */
" \n"
" stmdb sp!, {r3, r14} \n"
" mov r0, %0 \n"
" msr basepri, r0 \n"
" bl vTaskSwitchContext \n"
" mov r0, #0 \n"
" msr basepri, r0 \n"
" ldmia sp!, {r3, r14} \n"
" \n"/* Restore the context, including the critical nesting count. */
" ldr r1, [r3] \n"
" ldr r0, [r1] \n"/* The first item in pxCurrentTCB is the task top of stack. */
" ldmia r0!, {r4-r11} \n"/* Pop the registers. */
" msr psp, r0 \n"
" isb \n"
" bx r14 \n"
" \n"
" .align 4 \n"
"pxCurrentTCBConst: .word pxCurrentTCB \n"
::"i" ( configMAX_SYSCALL_INTERRUPT_PRIORITY )
);
}

OpenAI辅助理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void PendSV_Handler(void) {
/* 保存当前任务的上下文 */
__asm volatile (
"MRS R0, PSP \n" /* 获取进程堆栈指针 */
"STMDB R0!, {R4-R11} \n" /* 保存R4-R11寄存器到堆栈 */
"LDR R1, =pxCurrentTCB \n" /* 加载当前TCB指针地址 */
"LDR R2, [R1] \n" /* 加载当前TCB指针 */
"STR R0, [R2] \n" /* 保存堆栈指针到当前TCB */
);

/* 切换到下一个任务 */
vTaskSwitchContext();

/* 恢复下一个任务的上下文 */
__asm volatile (
"LDR R1, =pxCurrentTCB \n" /* 加载当前TCB指针地址 */
"LDR R2, [R1] \n" /* 加载当前TCB指针 */
"LDR R0, [R2] \n" /* 加载堆栈指针 */
"LDMIA R0!, {R4-R11} \n" /* 恢复R4-R11寄存器 */
"MSR PSP, R0 \n" /* 恢复进程堆栈指针 */
"BX LR \n" /* 返回任务 */
);
}

然后调用切换上下文函数

1
2
// task.c
void vTaskSwitchContext( void )

taskSELECT_HIGHEST_PRIORITY_TASK选择最高优先级任务

时钟管理

时钟节拍

MPS2_QEMU会调用处理程序xPortSysTickHandler

1
2
// port.c
void xPortSysTickHandler( void )

portDISABLE_INTERRUPTS关闭中断。

xTaskIncrementTick尝试增加tick值,成功后触发PendSV中断,强行进行一次调度。

portENABLE_INTERRUPTS打开中断。

1
2
// tasks.c
BaseType_t xTaskIncrementTick( void )

taskSWITCH_DELAYED_LISTS系统tick值反转时,将延时任务队列与反转反转队列互换。

判断是否有超时的任务,并将其移入就绪队列中。

任务延时

1
void vTaskDelay( const TickType_t xTicksToDelay )

vTaskSuspendAll关闭调度器。

prvAddCurrentTaskToDelayedList将当前任务加到延时列表中。

xTaskResumeAll恢复调度器。

portYIELD_WITHIN_API当恢复调度时未进行再次调度,则强制进行调度并让任务进入睡眠状态。

1
2
3
static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait,
const BaseType_t xCanBlockIndefinitely )

uxListRemove将当前任务移除队列中。

xTimeToWake = xConstTickCount + xTicksToWait计算下次唤醒的时刻。需要考虑时间反转的场景。