ARM 编译器 C 库启动和初始化
1 介绍
本文档描述了 C 库启动代码和在使用 ARM 编译器编译的应用程序启动期间可能调用的初始化函数。该文档概述了启动代码中的功能的作用以及它们存在的原因。您可以使用此文档来验证您的应用程序的启动代码。
1.1 版本
本文档描述了 ARM 编译器的启动代码。启动代码中的函数可能会在工具链的不同版本和补丁之间发生变化。本文档不保证库启动代码在后续版本或工具链补丁中的持续运行。
1.2 补充阅读
本节列出了 ARM 和第三方的出版物。请参阅 Infocenter,http://infocenter.arm.com,以访问 ARM 文档。
1.2.1 ARM 出版物
以下文档包含与本文档相关的信息:
• ARM 编译器工具链 ARM 处理器开发软件 (ARM DUI 0471)
• ARM 编译器工具链 ARM C 和 C++ 库和浮点支持参考 (ARM DUI 0492)
• 使用 ARM 的 ARM 编译器工具链C 和 C++ 库和浮点支持 (ARM DUI 0475)
• ARM 编译器工具链链接器参考 (ARM DUI 0493)。
2 启动代码
嵌入式应用程序需要在用户定义的 main() 函数启动之前进行初始化序列。这称为启动代码或引导代码。 ARM C 库包含启动应用程序所需的预编译和预汇编代码部分。链接您的应用程序时,链接器会根据应用程序从 C 库中包含必要的代码,以便为应用程序创建自定义启动代码。
笔记:在目标上运行的嵌入式应用程序可以在调用 C 库启动代码之前执行其他目标硬件初始化。有关详细信息,请参阅为 ARM 处理器开发软件中的重置和初始化。
一个应用程序的启动代码可能与另一个应用程序的启动代码不同。该文档没有描述任何特定用户应用程序的精确启动代码。此外,该文档没有描述如何自己自定义启动代码。有关如何自定义启动代码的信息,请参阅为 ARM 处理器开发软件。本文档中描述的启动代码适用于标准 ARM C 库。它不适用于 ARM C 微库。这对于 ARMv4T 及更高版本的架构也很常见。
3 C 库的入口点
函数 __main 是 C 库的入口点。除非您更改它,否则 __main 是 ARM 链接器 (armlink) 在创建映像时使用的 ELF 映像的默认入口点。图 1 显示了 C 库启动期间 __main 调用的函数。
图 1 C 库启动时调用的函数概述
__rt_entry 和 __rt_entry 调用的函数在 __rt_entry 调用的函数中进行了描述。
3.1 __scatterload
应用程序代码和数据可以位于根区域或非根区域。根区域具有相同的加载时间和执行时间地址。非根区域具有不同的加载时间和执行时间地址。根区域包含 ARM 链接器输出的区域表。区域表包含需要初始化的非根代码和数据区域的地址。区域表还包含一个函数指针,该指针指示该区域需要什么初始化,例如复制、归零或解压缩函数。
__scatterload 遍历区域表并初始化各种执行时区域。功能:
• 将零初始化(ZI) 区域初始化为零
• 将非根代码和数据区域从加载时位置复制或解压缩到执行时区域。
__main 总是在启动期间调用此函数,然后再调用 __rt_entry。
3.2 参见
使用 ARM C 和 C++ 库和浮点支持:
• 初始化执行环境和执行应用程序。
ARM C 和 C++ 库和浮点支持参考:
• 线程安全的 C 库函数。
为 ARM 处理器开发软件:
• 为您的目标硬件定制映像内存映射
• 本地内存设置注意事项
• 应用程序启动
• 重置和初始化
• 分散加载描述文件
• 根区域。
4 __rt_entry 调用的函数
__main 调用 __rt_entry 来初始化栈、堆和其他 C 库子系统。 __rt_entry 调用各种初始化函数,然后调用用户级main()。
这列出了 _rt_entry 可以调用的函数。这些函数按它们被调用的顺序列出:
1. _platform_pre_stackheap_init
2. __user_setup_stackheap or setup the Stack Pointer (SP) by another method
3. _platform_post_stackheap_init
4. __rt_lib_init
5. _platform_post_lib_init
6. main()
7. exit()
_platform_* 函数不是标准 C 库的一部分。如果您定义了它们,那么链接器会在 __rt_entry 中调用它们。
main() 是用户级应用程序的入口点。寄存器 r0 和 r1 包含 main() 的参数。如果 main() 返回,则将其返回值传递给 exit() 并退出应用程序。
__rt_entry 还负责设置栈和堆。但是,设置堆栈和堆取决于用户指定的方法。堆栈和堆可以通过以下任何一种方法设置:
• 调用__user_setup_stackheap。这也获得了堆使用的内存边界(堆顶和堆底)。
• 使用符号__initial_sp 的值加载SP。
• 使用链接器分散文件中指定的ARM_LIB_STACK 或ARM_LIB_STACKHEAP 区域的顶部。
__rt_entry 和 __rt_lib_init 在 C 库中不作为完整函数存在。这些函数的一小部分存在于作为 C 库一部分的几个内部对象中。并非所有这些代码段都对给定的用户应用程序有用。链接器决定给定应用程序需要这些代码段的哪个子集,并仅在启动代码中包含这些段。链接器以正确的顺序放置这些部分,以根据用户应用程序的要求创建自定义的 __rt_entry 和 __rt_lib_init 函数。
__rt_lib_init 调用的函数在 __rt_lib_init 调用的函数中进行了描述。
4.1 _platform_pre_stackheap_init
标准 C 库不提供此功能,但您可以根据需要定义它。例如,您可以使用此功能设置硬件。 __rt_entry 在初始化堆栈和堆的代码之前调用此函数(如果您定义它)。
4.2 __user_setup_stackheap
此函数使您能够设置和返回初始堆栈和堆的位置。 C 库不提供此功能,但您可以根据需要定义它。 __rt_entry 如果您定义它或定义旧函数 __user_initial_stackheap,则调用此函数。如果定义 __user_initial_stackheap,则 C 库提供默认的 __user_setup_stackheap 作为 __user_initial_stackheap 函数的包装器。
4.3 _platform_post_stackheap_init
C 库不提供此功能,但您可以根据需要定义它。例如,您可以使用此功能设置硬件。 __rt_entry 在初始化堆栈和堆的代码之后调用此函数(如果您定义它)。
4.4 __rt_lib_init
此函数初始化各种 C 库子系统。它初始化引用的库函数,初始化语言环境,并在必要时为 main() 设置 argc 和 argv。 __rt_entry 总是在启动期间调用此函数。
如果使用 __user_setup_stackheap 或 __user_initial_stackheap 函数来设置堆栈指针和堆,则堆内存块的起始地址和结束地址分别作为参数传递给寄存器 r0 和 r1 中的 __rt_lib_init。
如果用户级 main() 需要,该函数分别在寄存器 r0 和 r1 中返回 argc 和 argv。
4.5 _platform_post_lib_init
C 库不提供此功能,但您可以根据需要定义它。例如,您可以使用此功能设置硬件。 __rt_entry 在调用 __rt_lib_init 之后和调用用户级 main() 函数之前调用此函数(如果您定义它)。
4.6 参见
C 库的入口点
为 ARM 处理器开发软件:
• 复位和初始化
• 应用程序启动
• 堆栈指针初始化
• 放置堆栈和堆。
ARM C 和 C++ 库和浮点支持参考:
• __rt_entry
• __user_setup_stackeheap()
• __rt_stackheap_init()
• __rt_lib_init()
• __rt_lib_shutdown()
• _sys_exit()
• 旧函数__user_initial_stackheap()。
使用 ARM C 和 C++ 库和浮点支持:
• 堆栈指针初始化和堆边界
• 执行环境的初始化和应用程序的执行
• 对 __user_initial_stackheap() 的传统支持。
5 __rt_lib_init 调用的函数
链接器包括来自内部目标文件的各种初始化代码部分,以创建自定义 __rt_lib_int 函数。仅当应用程序需要时,链接器才会在 __rt_lib_init 中放置一个函数。
这列出了 _rt_lib_init 可以调用的函数。这些函数按它们被调用的顺序列出:
1. _fp_init
2. _init_alloc
3. _rand_init
4. _get_lc_collate
5. _get_lc_ctype
6. _get_lc_monetary
7. _get_lc_numeric
8. _get_lc_time
9. _atexit_init
10. _signal_init
11. _fp_trap_init
12. _clock_init
13. _getenv_init
14. _initio
15. _ARM_get_argv
16. _alloca_initialize
17. _ARM_exceptions_init
18. __cpp_initialize__aeabi_
5.1 _fp_init
该函数通过设置 FP 状态字来初始化浮点环境。如果用户应用程序使用 VFP 硬件,该函数会初始化浮点状态和控制寄存器 (FPSCR)。如果应用程序使用软件 VFP,该函数会初始化内存中的 FP 状态字。 __rt_lib_init 总是在启动期间调用此函数。
如何调用 fp_init 取决于 ARM 编译器版本:
• ARM 编译器 v4.1
fp_init 始终在启动期间调用。
• ARM Compiler 5
_fp_init 在启动期间被调用,除非您同时使用softfp 和不带状态字的FP 模型(--fpmode={ieee_no_fenv,std,fast})。在这种情况下,完全省略了对 _fp_init 的调用。
5.2 _init_alloc
此函数设置 malloc、free 和其他相关函数使用的数据结构。该函数有 2 个参数。寄存器 r0 中的第一个参数是堆内存块(heapbase)的开始,寄存器 r1 中的第二个参数是堆内存块(heaptop)的结束。如果这些堆绑定参数未作为参数传递给 __rt_lib_init,则 __rt_lib_init 使用符号 __heap_base 和 __heap_limit 或特殊的分散加载区域加载它们,请参阅 __rt_entry 调用的函数。如果应用程序使用堆,__rt_lib_init 调用此函数。
5.3 _rand_init
此函数将随机数生成器初始化为其默认起始状态。如果应用程序使用 rand(),__rt_lib_init 调用此函数。
5.4 _get_lc_collate
此函数获取指向包含 LC_COLLATE 语言环境类别设置的默认数据块的指针。它将指针插入到 C 库存储的语言环境指针变量中。如果应用程序调用其行为取决于此区域设置的任何函数,__rt_lib_init 将调用此函数。
5.5 _get_lc_ctype
此函数获取指向包含 LC_CTYPE 语言环境类别设置的默认数据块的指针。它将指针插入到 C 库存储的语言环境指针变量中。如果应用程序调用其行为取决于此语言环境设置的任何函数,__rt_lib_init 将调用此函数。
5.6 _get_lc_monetary
此函数获取指向包含 LC_MONETARY 语言环境类别设置的默认数据块的指针。它将指针插入到 C 库存储的语言环境指针变量中。如果应用程序调用其行为取决于此区域设置的任何函数,__rt_lib_init 将调用此函数。
5.7 _get_lc_numeric
此函数获取指向包含 LC_NUMERIC 语言环境类别设置的默认数据块的指针。它将指针插入到 C 库存储的语言环境指针变量中。如果应用程序调用其行为取决于此语言环境设置的任何函数,__rt_lib_init 将调用此函数。
5.8 _get_lc_time
此函数获取指向包含 LC_TIME 语言环境类别设置的默认数据块的指针。它将指针插入到 C 库存储的语言环境指针变量中。如果应用程序调用其行为取决于此语言环境设置的任何函数,__rt_lib_init 将调用此函数。
5.9 _atexit_init
此函数为传递给 atexit() 的函数指针设置 C 库的存储。在多线程应用程序中,它还设置了互斥锁以保护存储免受并发访问。如果应用程序使用 atexit(),__rt_lib_init 调用此函数。
5.10 _signal_init
此函数设置包含每个信号编号的当前处理程序的存储。在多线程应用程序中,它还设置了互斥锁以保护此存储免受并发访问。如果应用程序使用 signal(),__rt_lib_init 调用此函数。
5.11 _fp_trap_init
此函数设置库的存储,其中包含每种浮点异常的当前处理程序。在多线程应用程序中,它还设置了互斥锁以保护此存储免受并发访问。如果应用程序使用捕获的浮点异常,__rt_lib_init 将调用此函数,例如,如果您使用以下任何一种:
• --fpmode=ieee_full
• --fpmpde=ieee_fixed。
5.12 _clock_ini
该函数读取clock() 使用的定时器的当前值。这被存储为程序的开始时间。随后对clock() 的调用返回自程序开始时间以来经过的时间。这些是clock() 和_clock_init 的默认实现。您可以以不同的方式重新实现它们。如果应用程序使用clock(),__rt_lib_init 调用这个函数。
5.13 _getenv_init
标准 C 库不提供此功能,但您可以根据需要定义它。此函数使 getenv() 能够检索任何需要的数据。 __rt_lib_init 如果您定义它,则调用此函数。
5.14 _initio
此函数设置 stdio 内部状态。这包括初始化打开文件列表,调用 _sys_open() 打开三个标准流。如果应用程序使用 stdio,__rt_lib_init 会调用此函数。
5.15 __ARM_get_argv
此函数获取传递给 main() 的 argc 和 argv 值。该函数分别在寄存器 r0 和 r1 中返回 argc 和 argv。该函数可能会在寄存器 r2 和 r3 中返回另外两个参数。如果 main() 用参数声明,__rt_lib_init 调用此函数。
__ARM_get_argv 调用 _sys_command_string 以获取作为单个字符串的参数列表。然后它将这个字符串分解为每个单词的单独字符串。
5.16 __alloca_initialize
此函数将 alloca 列表指针设置为 NULL。如果使用基于 RVCT 堆的分配,__rt_lib_init 将调用此函数。
5.17 __ARM_exceptions_init
此函数设置 C++ 异常处理状态。如果应用程序使用 C++ 异常,__rt_lib_init 会调用此函数。
5.18 __cpp_initialize__aeabi_
此函数调用顶级 C++ 对象的构造函数。如果应用程序具有顶级 C++ 对象,__rt_lib_init 将调用此函数。
5.19 参见
__rt_entry 调用的函数
使用 ARM C 和 C++ 库和浮点支持:
• __rt_fp_status_addr()
• 在利用 C 库时使用 malloc()
• 从裸机 C 使用堆实现
• C 库中语言环境数据块的定义
• C++ 初始化、构造和销毁
• 执行环境的初始化和应用程序的执行
• 异常系统初始化
• 在C 库中定制语言环境函数的汇编器宏。
ARM C 和 C++ 库和浮点支持参考:
• _getenv_init()
• getenv()
• _clock_init()
• _findlocale()
• _sys_command_string()
• __rt_lib_init。
链接器参考:
• --ref_cpp_init, --no_ref_cpp_init。
6 附录
本附录提供了在应用程序启动期间可能调用的各种函数的摘要。它还显示该函数何时包含在启动代码中。
表1 启动功能汇总
符号名称
描述
包含在启动代码中
__alloca_initialize
将 alloca 列表指针设置为 NULL
使用基于堆的 alloca 时
__ARM_exceptions_init
设置异常处理状态
使用 C++ 异常时
__ARM_get_argv
获取 main() 的 argc 和 argv 值
如果 main() 使用参数定义
_atexit_init
为函数指针设置存储
使用 atexit() 时
_clock_init
读取由 clock() 使用的计时器的当前值
使用 clock() 时
__cpp_initialize__aeabi_
调用顶级 C++ 构造函数
当使用顶级 C++ 对象时
_fp_init
初始化浮点环境
始终
_fp_trap_init
为浮点异常处理程序设置存储
使用捕获的浮点异常时
_get_lc_collate
存储指向包含 LC_COLLATE 设置的数据块的指针
当使用依赖于整理区域设置的函数时
_get_lc_ctype
存储指向包含 LC_CTYPE 的数据块的指针settings
使用依赖于 ctype 的功能时区域设置
_get_lc_monetary
存储指向包含 LC_MONETARY 设置的数据块的指针
当使用依赖于货币区域设置的函数时
_get_lc_numeric
存储指向包含 LC_NUMERIC 设置的数据块的指针
当使用依赖于数字区域设置的函数时
_get_lc_time
存储指向包含 LC_TIME 设置的数据块的指针
使用依赖于时间区域设置的函数时
_getenv_init
使 getenv() 能够自行初始化
如果定义 _getenv_init
_init_alloc
设置 malloc、free 和其他相关函数使用的数据结构
使用堆时
_initio
设置 stdio 内部状态
使用 stdio 时
_rand_init
初始化随机数生成器
使用 rand() 时
_platform_post_lib_init
在 __rt_lib_init 之后启用初始化
如果您定义 _platform_post_lib_init
_platform_post_stackheap_init
在堆栈初始化后启用初始化
如果您定义 _platform_post_stackheap_init
_platform_pre_stackheap_init
在堆栈初始化之前启用初始化
如果定义 _platform_pre_stackheap_init
__rt_entry
设置运行时环境,然后调用 main() 始终 __rt_lib_init 调用必要的 C 库初始化函数
始终
__scatterload
将代码和数据从加载区域复制到执行区域
始终
_signal_init
为信号处理程序设置存储
使用 signal() 时
_user_setup_stackheap
设置堆栈和堆
如果您定义 _user_setup_stackheap