启动文件和链接脚本
1)启动文件
启动文件里面使用的是汇编语言,汇编语言常常可以分为两个部分语法风格和而不同的toolchain有不同的汇编语法风格,通常分配unified 和 非 unified。常见的工具包有 ARM toolchains 和 GNU toolchains 。比如 keil中使用的就是 ARM toolchains 也就是 MDK-ARM,而在一些开源的平台比如espidf,platform,rtthread-studio, stm32cube-ide等等使用的是开源的工具包 ARM tool chains。
/**
******************************************************************************
* @file startup_stm32f407xx.s
* @author MCD Application Team
* @brief STM32F407xx Devices vector table for GCC based toolchains.
* This module performs:
* - Set the initial SP
* - Set the initial PC == Reset_Handler,
* - Set the vector table entries with the exceptions ISR address
* - Branches to main in the C library (which eventually
* calls main()).
* After Reset the Cortex-M4 processor is in Thread mode,
* priority is Privileged, and the Stack is set to Main.
******************************************************************************
* @attention
*
* <h2><center>© COPYRIGHT 2017 STMicroelectronics</center></h2>
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of STMicroelectronics nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* 通过查阅Cortex M4内核手册 的指令集,
******************************************************************************
*/
// unified assembler language 简称为UAL 是为了兼容THUMB 和 ARM两种指令集而产生的一种统一汇编语言
// 汇编语言分为(pre-unified 和 unified),pre-unified指的是unified之前的汇编语法风格。
// 所需的编译语言的语法取决于所选择的开发工具,keil 中 MDK-ARM 同时支持上述两种语法。
// 所以启动文件的编写与开发平台有关
// 但是指令集与处理器的架构有关
// 所以汇编语言分为:语法风格和指令集两个部分
.syntax unified
.cpu cortex-m4
.fpu softvfp
.thumb
/*定义一个全局性的符号 */
.global g_pfnVectors
.global Default_Handler
/* start address for the initialization values of the .data section.
defined in linker script */
.word _sidata // 定义一个字的变量 , 这个变量的具体值在链接脚本之中
/* start address for the .data section. defined in linker script */
.word _sdata
/* end address for the .data section. defined in linker script */
.word _edata
/* start address for the .bss section. defined in linker script */
.word _sbss
/* end address for the .bss section. defined in linker script */
.word _ebss
/* stack used for SystemInit_ExtMemCtl; always internal RAM used */
/**
* @brief This is the code that gets called when the processor first
* starts execution following a reset event. Only the absolutely
* necessary set is performed, after which the application
* supplied main() routine is called.
* @param None
* @retval : None
* 程序刚上电时调用的第一个程序,也就是复位
*/
.section .text.Reset_Handler
/*这里的weak 类似于c语言中的weak 如果c中没有定义,那么这里默认的Reset_Handler将被调用 */
.weak Reset_Handler
.type Reset_Handler, %function
Reset_Handler:
ldr sp, =_estack /* set stack pointer */
/* Copy the data segment initializers from flash to SRAM */
/*
MEMORY
ROM (rx) : ORIGIN = 0x08000000, LENGTH = 1024k / 1024K flash /
RAM (rw) : ORIGIN = 0x20000000, LENGTH = 128k / 128K sram /
*/
movs r1, #0
b LoopCopyDataInit /* 无条件跳转到对应的标签 */
/* 数据段初始化,将代码段中的rw-data和zi-data段加载到sram中 为程序运行做准备 */
CopyDataInit:
ldr r3, =_sidata
ldr r3, [r3, r1] // 加载地址 [r3+r1] 中的数据到 r3寄存器
str r3, [r0, r1] // 将寄存器r3的值 存储到[r0+r1]地址中
adds r1, r1, #4 // r1 移动4个字节,也就是32bits
LoopCopyDataInit:
ldr r0, =_sdata
ldr r3, =_edata
adds r2, r0, r1 /* r2 = r0 + r1 三操作数是在这种汇编语法风格下才有的 */
cmp r2, r3
bcc CopyDataInit
/*初始化bss段中的 静态变量和全局变量,也就是全为0 */
ldr r2, =_sbss
b LoopFillZerobss
/* Zero fill the bss segment. */
FillZerobss:
movs r3, #0
str r3, [r2], #4
LoopFillZerobss:
ldr r3, = _ebss
cmp r2, r3
bcc FillZerobss
/* Call the clock system intitialization function.*/
bl SystemInit
/* Call static constructors */
/* bl __libc_init_array */
/* Call the application's entry point.
* 这里调用的Entry是 c语言的入口 进入这个函数之后就是c语言了
*/
bl entry
/* main 函数结束后 如果没有死循环 就会运行到这里 */
bx lr
.size Reset_Handler, .-Reset_Handler // 告诉连接器的位置
/**
* @brief This is the code that gets called when the processor receives an
* unexpected interrupt. This simply enters an infinite loop, preserving
* the system state for examination by a debugger.
* @param None
* @retval None
*/
.section .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:
b Infinite_Loop
.size Default_Handler, .-Default_Handler
/******************************************************************************
*
* The minimal vector table for a Cortex M3. Note that the proper constructs
* must be placed on this to ensure that it ends up at physical address
* 0x0000.0000.
* 在指定的内存区域定义中断向量表
*******************************************************************************/
.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 0
.word 0
.word 0
.word 0
.word SVC_Handler
.word DebugMon_Handler
.word 0
.word PendSV_Handler
.word SysTick_Handler
/* External Interrupts */
.word WWDG_IRQHandler /* Window WatchDog */
.word PVD_IRQHandler /* PVD through EXTI Line detection */
.word TAMP_STAMP_IRQHandler /* Tamper and TimeStamps through the EXTI line */
.word RTC_WKUP_IRQHandler /* RTC Wakeup through the EXTI line */
.word FLASH_IRQHandler /* FLASH */
.word RCC_IRQHandler /* RCC */
.word EXTI0_IRQHandler /* EXTI Line0 */
.word EXTI1_IRQHandler /* EXTI Line1 */
.word EXTI2_IRQHandler /* EXTI Line2 */
.word EXTI3_IRQHandler /* EXTI Line3 */
.word EXTI4_IRQHandler /* EXTI Line4 */
.word DMA1_Stream0_IRQHandler /* DMA1 Stream 0 */
.word DMA1_Stream1_IRQHandler /* DMA1 Stream 1 */
.word DMA1_Stream2_IRQHandler /* DMA1 Stream 2 */
.word DMA1_Stream3_IRQHandler /* DMA1 Stream 3 */
.word DMA1_Stream4_IRQHandler /* DMA1 Stream 4 */
.word DMA1_Stream5_IRQHandler /* DMA1 Stream 5 */
.word DMA1_Stream6_IRQHandler /* DMA1 Stream 6 */
.word ADC_IRQHandler /* ADC1, ADC2 and ADC3s */
.word CAN1_TX_IRQHandler /* CAN1 TX */
.word CAN1_RX0_IRQHandler /* CAN1 RX0 */
.word CAN1_RX1_IRQHandler /* CAN1 RX1 */
.word CAN1_SCE_IRQHandler /* CAN1 SCE */
.word EXTI9_5_IRQHandler /* External Line[9:5]s */
.word TIM1_BRK_TIM9_IRQHandler /* TIM1 Break and TIM9 */
.word TIM1_UP_TIM10_IRQHandler /* TIM1 Update and TIM10 */
.word TIM1_TRG_COM_TIM11_IRQHandler /* TIM1 Trigger and Commutation and TIM11 */
.word TIM1_CC_IRQHandler /* TIM1 Capture Compare */
.word TIM2_IRQHandler /* TIM2 */
.word TIM3_IRQHandler /* TIM3 */
.word TIM4_IRQHandler /* TIM4 */
.word I2C1_EV_IRQHandler /* I2C1 Event */
.word I2C1_ER_IRQHandler /* I2C1 Error */
.word I2C2_EV_IRQHandler /* I2C2 Event */
.word I2C2_ER_IRQHandler /* I2C2 Error */
.word SPI1_IRQHandler /* SPI1 */
.word SPI2_IRQHandler /* SPI2 */
.word USART1_IRQHandler /* USART1 */
.word USART2_IRQHandler /* USART2 */
.word USART3_IRQHandler /* USART3 */
.word EXTI15_10_IRQHandler /* External Line[15:10]s */
.word RTC_Alarm_IRQHandler /* RTC Alarm (A and B) through EXTI Line */
.word OTG_FS_WKUP_IRQHandler /* USB OTG FS Wakeup through EXTI line */
.word TIM8_BRK_TIM12_IRQHandler /* TIM8 Break and TIM12 */
.word TIM8_UP_TIM13_IRQHandler /* TIM8 Update and TIM13 */
.word TIM8_TRG_COM_TIM14_IRQHandler /* TIM8 Trigger and Commutation and TIM14 */
.word TIM8_CC_IRQHandler /* TIM8 Capture Compare */
.word DMA1_Stream7_IRQHandler /* DMA1 Stream7 */
.word FSMC_IRQHandler /* FSMC */
.word SDIO_IRQHandler /* SDIO */
.word TIM5_IRQHandler /* TIM5 */
.word SPI3_IRQHandler /* SPI3 */
.word UART4_IRQHandler /* UART4 */
.word UART5_IRQHandler /* UART5 */
.word TIM6_DAC_IRQHandler /* TIM6 and DAC1&2 underrun errors */
.word TIM7_IRQHandler /* TIM7 */
.word DMA2_Stream0_IRQHandler /* DMA2 Stream 0 */
.word DMA2_Stream1_IRQHandler /* DMA2 Stream 1 */
.word DMA2_Stream2_IRQHandler /* DMA2 Stream 2 */
.word DMA2_Stream3_IRQHandler /* DMA2 Stream 3 */
.word DMA2_Stream4_IRQHandler /* DMA2 Stream 4 */
.word ETH_IRQHandler /* Ethernet */
.word ETH_WKUP_IRQHandler /* Ethernet Wakeup through EXTI line */
.word CAN2_TX_IRQHandler /* CAN2 TX */
.word CAN2_RX0_IRQHandler /* CAN2 RX0 */
.word CAN2_RX1_IRQHandler /* CAN2 RX1 */
.word CAN2_SCE_IRQHandler /* CAN2 SCE */
.word OTG_FS_IRQHandler /* USB OTG FS */
.word DMA2_Stream5_IRQHandler /* DMA2 Stream 5 */
.word DMA2_Stream6_IRQHandler /* DMA2 Stream 6 */
.word DMA2_Stream7_IRQHandler /* DMA2 Stream 7 */
.word USART6_IRQHandler /* USART6 */
.word I2C3_EV_IRQHandler /* I2C3 event */
.word I2C3_ER_IRQHandler /* I2C3 error */
.word OTG_HS_EP1_OUT_IRQHandler /* USB OTG HS End Point 1 Out */
.word OTG_HS_EP1_IN_IRQHandler /* USB OTG HS End Point 1 In */
.word OTG_HS_WKUP_IRQHandler /* USB OTG HS Wakeup through EXTI */
.word OTG_HS_IRQHandler /* USB OTG HS */
.word DCMI_IRQHandler /* DCMI */
.word 0 /* CRYP crypto */
.word HASH_RNG_IRQHandler /* Hash and Rng */
.word FPU_IRQHandler /* FPU */
/*******************************************************************************
*
* Provide weak aliases for each Exception handler to the Default_Handler.
* As they are weak aliases, any function with the same name will override
* this definition.
*
*******************************************************************************/
.weak NMI_Handler
.thumb_set NMI_Handler,Default_Handler
.........
.........
.weak HASH_RNG_IRQHandler
.thumb_set HASH_RNG_IRQHandler,Default_Handler
.weak FPU_IRQHandler
.thumb_set FPU_IRQHandler,Default_Handler
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
2)链接脚本
链接脚本的语法通常是特定于链接器的,不同的链接器可能具有不同的语法。在嵌入式开发中,常见的链接器包括GNU ld(GNU链接器)和Keil MDK等,它们具有不同的链接脚本语法。
链接脚本的目的是确保生成的可执行文件在目标设备上正确运行,包括正确加载代码、数据和堆栈,并保证程序的入口点正确。链接脚本是嵌入式系统中非常重要的一部分,它确保程序能够有效地利用设备的存储器资源。不同的嵌入式开发工具和平台可能有不同的链接脚本语法,因此需要根据具体工具和目标设备来编写或配置链接脚本。
/*
* linker script for STM32F407VG with GNU ld
*/
/* Program Entry, set to mark it as "used" and avoid gc */
MEMORY
{
ROM (rx) : ORIGIN = 0x08000000, LENGTH = 1024k /* 1024K flash */
RAM (rw) : ORIGIN = 0x20000000, LENGTH = 128k /* 128K sram */
}
/* 指定c程序运行的第一个函数 从Reset_Handler开始运行 即会加载到单片机的启动地址 0x8000 0000上*/
ENTRY(Reset_Handler)
_system_stack_size = 0x400;
SECTIONS
{
/*代码段*/
.text :
{
. = ALIGN(4);
_stext = .;
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
*(.text) /* remaining code */
*(.text.*) /* remaining code */
*(.rodata) /* read-only data (constants) */
*(.rodata*)
*(.glue_7)
*(.glue_7t)
*(.gnu.linkonce.t*)
/* section information for finsh shell */
. = ALIGN(4);
__fsymtab_start = .;
KEEP(*(FSymTab))
__fsymtab_end = .;
. = ALIGN(4);
__vsymtab_start = .;
KEEP(*(VSymTab))
__vsymtab_end = .;
/* section information for utest */
. = ALIGN(4);
__rt_utest_tc_tab_start = .;
KEEP(*(UtestTcTab))
__rt_utest_tc_tab_end = .;
/* section information for at server */
. = ALIGN(4);
__rtatcmdtab_start = .;
KEEP(*(RtAtCmdTab))
__rtatcmdtab_end = .;
. = ALIGN(4);
/* section information for initial.
通过宏定义实现的系统初始化 !!!!
在c语言中 以 rti_fn 开头的宏定义
*/
. = ALIGN(4);
__rt_init_start = .;
KEEP(*(SORT(.rti_fn*)))
__rt_init_end = .;
. = ALIGN(4);
/*__ctors_start__ 分配了 C++ 全局构造函数段的起始地址, __ctors_end__ 分配了 C++ 全局构造函数段的结束地址*/
PROVIDE(__ctors_start__ = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
PROVIDE(__ctors_end__ = .);
. = ALIGN(4);
_etext = .; // _etext 存放代码段text的结束地址。
} > ROM = 0
/*异常*/
/* .ARM.exidx is sorted, so has to go in its own output section. */
__exidx_start = .;
.ARM.exidx :
{
/* *表示 通配符 表示以.ARM.exidx 或 .gnu.linkonce.armexidx 开头的段名称 */
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
/* This is used by the startup in order to initialize the .data secion */
_sidata = .;
} > ROM
__exidx_end = .;
/* .data section which is used for initialized data */
/*数据段*/
.data : AT (_sidata) // 使用 AT 指定.data段的起始地址为 _sidata 标识符所表示的地址
{
. = ALIGN(4);
/* This is used by the startup in order to initialize the .data secion */
_sdata = . ;
*(.data)
*(.data.*)
*(.gnu.linkonce.d*)
/*
给全局析构函数分配的段
在 C++ 程序中,析构函数用于对象的清理和资源释放。这些函数的指针通常存储在特定的段中,以便在程序退出时执行这些析构函数,
确保资源的正确释放。这段链接脚本的作用是将析构函数相关信息正确地放置在存储器中,以便在程序退出时执行这些析构函数
*/
PROVIDE(__dtors_start__ = .);
KEEP(*(SORT(.dtors.*)))
KEEP(*(.dtors))
PROVIDE(__dtors_end__ = .);
. = ALIGN(4);
/* This is used by the startup in order to initialize the .data secion */
_edata = . ;
} >RAM
/*堆栈段*/
.stack :
{
. = ALIGN(4);
_sstack = .;
. = . + _system_stack_size; // 即之前设置的系统堆栈段大小
. = ALIGN(4);
_estack = .;
} >RAM
/*全局变量和静态变量初始化为0的段*/
__bss_start = .;
.bss :
{
. = ALIGN(4);
/* This is used by the startup in order to initialize the .bss secion */
_sbss = .;
*(.bss)
*(.bss.*)
*(COMMON)
. = ALIGN(4);
/* This is used by the startup in order to initialize the .bss secion */
_ebss = . ;
*(.bss.init)
} > RAM
__bss_end = .;
_end = .;
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
/* DWARF debug sections.
* Symbols in the DWARF debugging sections are relative to the beginning
* of the section so we begin them at 0. */
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
}
使用链接脚本+宏函数实现函数的初始化
在rtthread的启动流程当中,其系统初始化模块可以通过一个宏定义
/* in file rtdef.h */
typedef int (*init_fn_t)(void);
struct rt_init_desc
{
const char* fn_name;
const init_fn_t fn;
};
#define INIT_EXPORT(fn, level) \
const char __rti_##fn##_name[] = #fn; \
RT_USED const struct rt_init_desc __rt_init_desc_##fn SECTION(".rti_fn." level) = \
{ __rti_##fn##_name, fn};
整体来看其中INIT_EXPORT时一个宏函数,fn为函数名,level为初始化优先级,数字越小优先级越高。
const char __rti_##fn##_name[] = #fn;
##fn## 表示 宏定义中字符串的连接,#fn 表示 获取其变量名的字符串 比如函数名为 my_function,那么上面一条语句将被翻译为
const char __rti_my_function_name[] = "my_function";
这意味在使用这个宏函数的地方定义了一个字符串常量 __rti_my_function_name 内容为 “my_function”。下面一句语句主要是定义了一个结构体变量,记录了要用于初始化的函数指针和函数名。
RT_USED const struct rt_init_desc __rt_init_desc_##fn SECTION(".rti_fn." level) = \
{ __rti_##fn##_name, fn};
如果传入的参数为fn为 my_function 上述语句将被翻译为:
RT_USED const struct rt_init_desc __rt_init_desc_myfunction SECTION(".rti_fn." level) = \
{ __rti_myfunction_name, fn};
意味着还定义了另一个结构体变量__rt_init_desc_myfunction,SECTION(“.rti_fn.” level) 的作用是确保初始化函数描述信息结构体被放置在与初始化函数描述信息相关的段中,以便在程序启动时正确执行这些初始化函数。具体的段名称和放置位置通常由链接脚本中的规则和约定确定。链接脚本会指定哪些数据应该放在哪些段中,以确保数据在内存中的正确位置。
总的来说上述宏函数干了两件事情,定义了两个变量一个是 ___rti_my_function_name 表示以字符串的形式存储函数名称,一个是结构体变量_____rt_init_desc_myfunction存储了函数的名字和其函数指针。
在链接脚本的代码段定义中有如下代码:
. = ALIGN(4);
__rt_init_start = .;
KEEP(*(SORT(.rti_fn*)))
__rt_init_end = .;
KEEP(*(SORT(.rti_fn*)))
中的 SORT
是链接脚本中的一个命令,它用于对输入节(sections)进行排序。在这个上下文中,.rti_fn*
是一个通配符,用于匹配以 .rti_fn
开头的节名称。
具体来说,这一行的作用是:
- 匹配以
.rti_fn
开头的所有节(sections),这些节通常用于存储初始化函数的描述信息。 - 使用
SORT
命令对这些匹配的节进行排序。
排序的目的是确保初始化函数按特定顺序执行,以便满足程序的初始化要求。通常,初始化函数的执行顺序可能会对程序的正确性产生影响,因此需要确保它们按照正确的顺序执行。
SORT
命令可以按字母顺序对节进行排序,或者按照链接脚本中的规则来排序。这可以确保初始化函数按照预定义的顺序执行,以满足程序的要求。
综上所述,SORT
命令用于链接脚本中对初始化函数描述信息的节进行排序,以确保初始化函数按照正确的顺序执行,排序的依据也就是在c语言的宏函数的level。也就是说SORT会按照在c语言中section函数中传入的参数对其进行排序,然后按照排好的顺序将其存储在对应的代码段中。但此时仅仅完成了不同级别需要初始化函数的存储位置,还没有真正的进行初始化。以 INIT_BOARD_EXPORT(fn)为例,由官方给出的系统初始化的流程图可知。在函数rt_components_board_init中对其导入的函数进行初始化。
void rt_components_board_init(void)
{
int result;
const struct rt_init_desc *desc;
for (desc = &__rt_init_desc_rti_board_start; desc < &__rt_init_desc_rti_board_end; desc ++)
{
rt_kprintf("initialize %s", desc->fn_name);
result = desc->fn();
rt_kprintf(":%d done\n", result);
}
}
接下来是最奇妙的地方,可见上述代码中存在 ____rt_init_desc_rti_board_start 和 ____rt_init_desc_rti_board_end两个变量,这两个变量也是通过INIT_EXPORT宏函数进行导入的。但是最重要的是其level值。见如下代码:
static int rti_start(void)
{
return 0;
}
/* 相当于一个地址的界定符,用于区分不同level的函数 用于for循环的比较*/
INIT_EXPORT(rti_start, "0");
static int rti_board_start(void)
{
return 0;
}
INIT_EXPORT(rti_board_start, "0.end");
static int rti_board_end(void)
{
return 0;
}
INIT_EXPORT(rti_board_end, "1.end");
static int rti_end(void)
{
return 0;
}
INIT_EXPORT(rti_end, "6.end");
通过上述代码定义了如下两个变量
RT_USED const struct rt_init_desc __rt_init_desc_rti_board_start;
RT_USED const struct rt_init_desc __rt_init_desc_rti_board_end;
另外我们查看代码
/* board init routines will be called in board_init() function */
#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1")
/* pre/device/component/env/app init routines will be called in init_thread */
/* components pre-initialization (pure software initilization) */
#define INIT_PREV_EXPORT(fn) INIT_EXPORT(fn, "2")
/* device initialization */
#define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "3")
/* components initialization (dfs, lwip, ...) */
#define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "4")
/* environment initialization (mount disk, ...) */
#define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "5")
/* appliation initialization (rtgui application etc ...) */
#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6")
可知所有的rt_components_board_init后板层需要初始化的函数的level都是1,所以根据链接文件的内存管理,会按照如下顺序进行分配空间:
其实,总的来说系统初始化的过程就是对内存空间的操作,利用对需要初始化的函数进行编号,然后通过链接脚本按照编号分配至连续的内存空间,其中对于需要在不同区域初始化的部分在内存中打标签也就是1.end,0.end,6.end等等。对于在同一区域初始化而不同顺序初始化的部分按照编号顺序来进行。