在FreeRtos中任务切换的本质是函数调用,CPU在指定时间内执行不同的函数,从微观上看每个任务都是顺序执行的,但是CPU运算能力很强,可以在很短时间内完成指令的执行,从宏观上看每个任务相当与同时在执行。
可以从任务创建含函数追述到任务控制块的成员都被指向了那。函数原型
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 )
在xTaskCreate函数中先对TCB控制块进行初始化。
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
然后对栈进行初始化
pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
然后调用了prvInitialiseNewTask函数进行初始化。函数原型
static void prvInitialiseNewTask( TaskFunction_t pxTaskCode,
const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask,
TCB_t * pxNewTCB,
const MemoryRegion_t * const xRegions )
在prvInitialiseNewTask函数中对pxTopOfStack进行判断处理,即根据栈的增长方向对pxTopOfStack和pxEndOfStack的值进行初始化。
在M3中configRECORD_STACK_HIGH_ADDRESS宏被定义成了1所以说明栈是高地址向低地址移动的(向下增长)。
在调用了pxPortInitialiseStack函数对任务的栈进行初始化,及保存CPU对应寄存器地址,函数原型。
StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack,
TaskFunction_t pxCode,
void * pvParameters )
可以看到任务函数的地址最终被指向了PC寄存器,也就是当前程序运行的位置,修改PC寄存器的值即可让程序运行到指定位置。
在freeRtos中是通过链表来管理任务的
在任务创建函数中最后调用了prvAddNewTaskToReadyList函数添加到就绪链表中,在进行链表操作时进入了临界区,保证不会被打断。
在该函数中如果调度器没有运行,当前任务控制块也就是当前任务一直是优先级最高的任务先执行,如果是同优先级,那么就是最后一个被创建的任务第一个执行。同时uxTopReadPriorty的值表示就绪链表中优先级最高的那个任务。
在创建完任务后,通过调用vTaskStartScheduler函数启动任务调度,该函数会创建一个空闲任务,空闲任务主要做两个事情
函数原型
static portTASK_FUNCTION( prvIdleTask, pvParameters )
最终vTaskStartScheduler函数会调用xPortStartScheduler函数,在该函数中将PendSV和SysTick中断设置为低优先级。
然后调用 vPortSetupTimerInterrupt函数设置SysTick中断产生的间隔。
然后又调用prvStartFirstTask函数将SP指向中断向量表,然后进入SVC执行第一个任务函数。
在xPortSysTickHandler中断服务函数中调用xTaskIncrementTick函数去判断是否要进行任务切换,如果切换就会返回pdTure,同时产生PendSV中断进行任务切换。