使用FreeRTOS也很长时间了,断断续续看过,现在记录,流程写出来不难,难的是进行高度简洁的总结。
在学校时,看过UCOS-II的代码,由于版权问题,不能使用。而FreeRTOS是基于 MIT open source license,可以免费发布,可以在商业中使用,在2019年EETimes调查中,FreeRTOS是使用量排名第三的OS。
先介绍task相关的部分
一个OS要把各个task进行管理,主要就完成2件事情,一是构造好代表各task的TCB和栈,二是进行线程切换时把上一个task的上下文保存好,并切换到下一个task,只要这2部分完成的没有问题,一个最简单的RTOS就实现了,至于各task的调度策略,支持事件等复杂功能,只是锦上添花。task的上下文保存在栈上,task的需要OS维护的信息保存在TCB中,因此一个task只使用TCB和栈就可以完全表示。
一:初始化
task创建就是把表示此task的TCB和栈初始化好,等待调度。task创建好后,TCB和栈如下:

第一个是栈指针,指向一个分配的内存,第一次初始化后,内容是要切换后执行的上下文。
xStateListItem 初始化为挂在对应优先级的Task链表上,这里没画出。Event为空,表示还没有事件。
二:任务第一次开启
是由vTaskStartScheduler()完成,先创建idle线程,之后调用 xPortStartScheduler(),在其中调用vStartFirstTask(),里面执行开中断,svc 2号调用,到svc服务函数中执行。
SVC_Handler 中调用 vPortSVCHandler_C,调用vRestoreContextOfFirstTask,流程如下:
-> r0 = pxCurrentTCB[0]; 获取当前的psp,即创建的main的线程
-> mpu 相关,这里暂只分析不开mpu
-> ldm r0!, {r1-r2}; /* Read from stack - r1 = PSPLIM and r2 = EXC_RETURN. */
-> msr psplim, r1; /* Set this task's PSPLIM value. */
-> movs r1, #2; msr CONTROL, r1; /* Switch to use PSP in the thread mode. */
-> adds r0, #32; msr psp, r0; isb; /* 调整栈指针,跳过R4~R11,见上图 */
-> mov r0, #0; msr basepri, r0; /* 确保终端是打开的 */
-> bx r2; /* 按 EXC_RETURN 返回,之前初始化是0xfffffffd,按中断中方式返回 */
注意返回时,硬件会自动从psp地址中弹出 R0~R3, R12, LR, PC,然后此时的CPU上下文就是线程创建时的栈的内容,然后CPU开始跑第一个线程。
三:任务切换
触发任务切换只有2种情况,第一个是被动触发,当中断发生(外设或tick中断),在中断中有信号量等释放,或计时到某个时间点,需要触发之前延时等待或超时的事件;第二个是主动触发,正在执行的线程执行了延时,或释放了信号量,或获取信号量未果等,主动触发了调度。这里举一个主动调用延时的例子:
vTaskDelay(xTicksToDelay)
-> prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
-> uxListRemove( &( pxCurrentTCB->xStateListItem ) ); 从当前链表上移除,当前链表为正执行的任务,如果不是正执行的任务,不会调用delay函数
-> vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) ); 将当前函数移到DelayedTaskList上
-> if( xTimeToWake < xNextTaskUnblockTime ) { xNextTaskUnblockTime = xTimeToWake; }
-> if( xAlreadyYielded == pdFALSE ) portYIELD_WITHIN_API(); 置位PENDSV中断,触发并跳入 pendsv 函数。
PendSV_Handler @ portasm.c src\kernel\FreeRTOSv10.4.x\Source\portable\GCC\ARM_CM33_NTZ\non_secure
-> mrs r0, psp; mrs r2, psplim; ... #保存了 r2, r3 = lr = 0xfffffffd(中断上下文),保存当前的 psp 到 TCB
-> mov r0, configMAX_SYSCALL_INTERRUPT_PRIORITY; msr basepri, r0; dsb; isb; 调整至最高优先级,以关闭中断
-> bl vTaskSwitchContext; @ tasks.c src\kernel\FreeRTOSv10.4.x\Source
-> taskSELECT_HIGHEST_PRIORITY_TASK();
-> while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) ) { --uxTopPriority; } 一直找到下一个最高优先级的task
-> listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); 使用 idle 的 xStateListItem 把 pxCurrentTCB 赋值为下一个最高优先级的task的TCB
-> uxTopReadyPriority = uxTopPriority;
-> mov r0, #0; msr basepri, r0; 开中断
-> ldr r2, pxCurrentTCB; ldr r1, [r2]; ldr r0, [r1];
-> ldmia r0!, {r2-r11} # 取出包括 r3 = return address
-> msr psplim, r2; #no MPU
-> msr psp, r0; bx r3; 执行idle task的返回地址,此时为 0xfffffffd ,即从中断返回,从psp取出上下文,开始执行下一个最高优先级的线程
本文详细介绍了FreeRTOS操作系统中任务的创建、初始化及调度过程。重点讲解了任务切换的实现机制,包括如何通过栈和TCB来表示任务状态,以及如何在中断和服务例程中进行上下文切换。
224

被折叠的 条评论
为什么被折叠?



