freertos手册_FreeRTOS内核之任务的启动

前言

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

eceb721f50067d530b8e0f46dea270b2.png

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

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

步入正轨

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

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

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

7f4731f2dfc7acc1ccf1d9a399777aa1.png

比如"${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_pfnVectorsg_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接口。

4de897e0766a3ff1175ff2948450bc0d.png

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

243f3a73634487927857782ed3310d40.png

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

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

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

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

.section .text.thumb.align 4SVC_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,会在后面的任务创建里讲到,目前只需要知道的就是它代表当前需要执行的任务。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值