freeRtos源码分析之调度原理
1.任务切换的本质
在FreeRtos中任务切换的本质是函数调用,CPU在指定时间内执行不同的函数,从微观上看每个任务都是顺序执行的,但是CPU运算能力很强,可以在很短时间内完成指令的执行,从宏观上看每个任务相当与同时在执行。
1.1 在ram-M3架构下函数调用原理
2.任务的几种状态
- 运行态:running
- 就绪态:ready
- 阻塞:blocked,等待某件事(时间、事件)
- 暂停:suspend,休息去了
3.任务控制块TCB
TCB_t用来表示一个任务,它的重要成员如下:
- pxTopOfStack:栈顶。
- xStateListItem:通过它把当前任务放入某个状态链表(Ready, Blocked, Suspended)
- xEventListItem:比如任务在等待队列A,则通过xEventListItem把自己放入队列A的链表
- uxPriority:任务的原始优先级
- pxStack:栈的起始位置
- pxEndOfStack:栈底,有效地址
- uxBasePriority:任务的当前优先级
- pcTaskName:任务名称
4.任务创建函数
可以从任务创建含函数追述到任务控制块的成员都被指向了那。函数原型
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寄存器的值即可让程序运行到指定位置。
5.任务调度
5.1任务怎么管理
在freeRtos中是通过链表来管理任务的
在任务创建函数中最后调用了prvAddNewTaskToReadyList函数添加到就绪链表中,在进行链表操作时进入了临界区,保证不会被打断。
在该函数中如果调度器没有运行,当前任务控制块也就是当前任务一直是优先级最高的任务先执行,如果是同优先级,那么就是最后一个被创建的任务第一个执行。同时uxTopReadPriorty的值表示就绪链表中优先级最高的那个任务。
5.2启动任务调度函数
在创建完任务后,通过调用vTaskStartScheduler函数启动任务调度,该函数会创建一个空闲任务,空闲任务主要做两个事情
函数原型
static portTASK_FUNCTION( prvIdleTask, pvParameters )
-
当前优先级为0的就绪链表中如果有大于1个任务,那么空闲任务会主动触发一次任务切换。
-
如果有任务删除了自己,那么空闲任务会释放该任务对应的资源。
最终vTaskStartScheduler函数会调用xPortStartScheduler函数,在该函数中将PendSV和SysTick中断设置为低优先级。
然后调用 vPortSetupTimerInterrupt函数设置SysTick中断产生的间隔。
然后又调用prvStartFirstTask函数将SP指向中断向量表,然后进入SVC执行第一个任务函数。
5.3任务间如何切换
在xPortSysTickHandler中断服务函数中调用xTaskIncrementTick函数去判断是否要进行任务切换,如果切换就会返回pdTure,同时产生PendSV中断进行任务切换。