前言

现在开始正式剖析FreeRTOS内核,原计划使用基于Windows仿真环境的基础代码,但进行分析时,发现找不到调用main函数的地方,有点摸不着头脑,没办法,职业病,总想知道个来龙去脉,也不知道仿真环境的运行机制,汗颜,看来得改变策略了,内核剖析使用基于真实平台的代码,如果需要纯软件的实操就用Windows仿真环境(应该是可以跑起来的)。基于本人目前的工作经验,对ARMv8架构再熟悉不过了,选择了类似的基于ARMv7-M架构Cortex-M4 核的如下平台(源自Amazon FreeRTOS控制台) :

freertos开发用什么开发工具(FreeRTOS内核之任务的启动)(1)

这份代码就比较容易理清楚了,查找main函数的调用关系即可追踪到系统启动的第一条指令,权当是开胃小菜吧。

本文将介绍从CPU第一条指令到开始进行内核任务调度。

步入正轨

开始前,下面两个问题有必要先弄清楚,才方便后面的流程梳理:

A. 有些接口存在于多个文件,怎么知道本项目的接口存在于哪些文件里?

这一点很简单,查看相关单板平台的CMakeList.txt文件即可,比如我这套代码用到的部分代码路径如下:

freertos开发用什么开发工具(FreeRTOS内核之任务的启动)(2)

比如"${AFR_KERNEL_DIR}/portable/"下是适配不同CPU的代码,很多是同一接口的在不同CPU上的实现,从上面的路径就可以知道本项目里的接口在"${AFR_KERNEL_DIR}/portable/GCC/ARM_CM4F" 下的文件里。

B. CPU起来后,第一条指令在哪里?

这就涉及到链接器使用的链接脚本,本平台的链接脚本为STM32L475VGTx_FLASH.ld(链接脚本后缀一般为*.ld,或*.lds),摘取部分示意如下:

.... /* Specify the memory areas */ MEMORY { RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 96K RAM2 (xrw) : ORIGIN = 0x10000000, LENGTH = 32K FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 464K /* Use only the first bank */ FLASH_UC (r) : ORIGIN = 0x08074000, LENGTH = 9K /* Fixed-location area */ } /* Define output sections */ SECTIONS { ... /* The startup code goes first into FLASH */ .isr_vector : /*存放CPU向量表的节区*/ { . = ALIGN(8); KEEP(*(.isr_vector)) /* Startup code */ . = ALIGN(8); } >FLAS /* The program code and other data goes into FLASH */ .text : { . = ALIGN(8); *(.text) /* .text sections (code) */ ... . = ALIGN(8); _etext = .; /* define a global symbols at end of code */ } >FLASH ... }

该文件指定编译生成的各个段的在FLASH或RAM里的存放位置(关于链接脚本的语法,网上的资料很多了:)。CPU起来后直接跳转到复位向量处的指令,复位向量位于CPU的向量表里,该向量表一般定义在各个平台的汇编文件里,当前这个平台的代码在文件"startup_stm32l475xx.s":

.section .isr_vector,"a",%progbits .type g_pfnVectors, %object .size g_pfnVectors, .-g_pfnVectors g_pfnVectors: .word _estack .word Reset_Handler //复位向量 .word NMI_Handler .word HardFault_Handler .word MemManage_Handler .word BusFault_Handler .word UsageFault_Handler ... .word SVC_Handler //SVC异常向量 ...

这里定义了复位向量表,段名命为".isr_vector",根据前面提供的链接脚本,该段将被放置到FLASH零偏移处。根据下面的Cortex-M4的手册里的向量表,复位向量位于向量表偏移4字节处,即对应这里的Reset_Handler接口。

freertos开发用什么开发工具(FreeRTOS内核之任务的启动)(3)

这样,CPU起来后,直接从0x4偏移处取代码执行!下面开始正餐了,那么从第一条指令到第一个任务有多远?来看看下面这张流程图:

freertos开发用什么开发工具(FreeRTOS内核之任务的启动)(4)

这么看也没有多少路,到少比我想像的少得多。这里创建了三个任务,分别是打印任务"Logging",空闲任务"IDLE"和定时服务任务"Tmr Svc"。

对于最后一步,是怎么启动第一个任务的呢?看下接口vPortStartFirstTask()的实现:

__asm volatile ( " ldr r0, =0xE000ED08 \n" /* Use the NVIC offset register to locate the stack. */ " ldr r0, [r0] \n" " ldr r0, [r0] \n" " msr msp, r0 \n" /* Set the msp back to the start of the stack. */ " mov r0, #0 \n" /* Clear the bit that indicates the FPU is in use, see comment above. */ " msr control, r0 \n" " cpsie i \n" /* Globally enable interrupts. */ " cpsie f \n" " dsb \n" " isb \n" " svc 0 \n" /* System call to start first task. */ " nop \n" );

关键在于"svc"指令,要理解这个含义,就又需回到前面讲到的CPU的向量表了,通过分析CORTEX-M4资料,可以知道SVC指令将触发一个异常,该异常向量的地址为SVC_Handler函数的地址,该接口也很短,直接贴这里了:

.section .text .thumb .align 4 SVC_Handler: .type func ;Get the location of the current TCB. ldr.w r3, =pxCurrentTCB ldr r1, [r3] ldr r0, [r1] ;Pop the core registers. ldmia r0!, {r4-r11, r14} msr psp, r0 isb mov r0, #0 msr basepri, r0 bx r14 .size SVC_Handler, $-SVC_Handler .endsec

可见,该接口主要是恢复pxCurrentTCB对应任务的上下文,并执行该任务,到此,第一个任务就开始跑了,关于pxCurrentTCB,会在后面的任务创建里讲到,目前只需要知道的就是它代表当前需要执行的任务。

,