【问题标题】:why need linker script and startup code?为什么需要链接描述文件和启动代码?
【发布时间】:2017-05-12 22:01:09
【问题描述】:
【问题讨论】:
标签:
gcc
embedded
startup
linker-scripts
【解决方案1】:
1) .data 部分包含变量。变量是变量——它们在运行时发生变化。这些变量需要在 RAM 中,以便它们可以在运行时轻松更改。闪存与 RAM 不同,在运行时不容易更改。闪存包含 .data 部分中变量的初始值。启动代码将 .data 部分从闪存复制到 RAM 以初始化 RAM 中的运行时变量。
2) 链接器脚本:您的编译器创建的目标代码尚未定位到微控制器的内存映射中。这是链接器的工作,这就是您需要链接器脚本的原因。链接描述文件是链接器的输入,并提供一些关于系统内存位置和范围的指令。
启动代码:从main 开始的 C 程序不会在真空中运行,而是对环境做出一些假设。例如,它假定已初始化的变量在main 执行之前已经初始化。当main 执行时(即“运行时环境”),启动代码对于放置所有假定的东西是必要的。堆栈指针是在启动代码中初始化的另一个例子,在main 执行之前。如果您使用的是 C++,那么在 main 执行之前,会从启动代码中调用静态对象的构造函数。
【解决方案2】:
1) 为什么我们需要加载地址和运行时地址。
虽然在大多数情况下可以从内存映射 ROM 运行代码,但从 RAM 运行代码通常会更快。在某些情况下,可能还有更大的 RAM,ROM 和应用程序代码可能会压缩到 ROM 中,因此可执行代码可能不会简单地从 ROM 复制并解压缩 - 允许比可用 ROM 更大的应用程序。
在代码存储在非内存映射的大容量存储介质(如 NAND 闪存)上的情况下,它在任何情况下都不能直接执行,必须通过某种引导加载程序加载到 RAM 中。
2) 为什么我们需要链接描述文件和启动代码。我不能像下面那样构建 C 源代码并使用 qemu 运行它吗?
链接描述文件定义了您的目标和应用程序的内存布局。由于本教程是针对裸机编程的,因此没有操作系统可以为您处理。类似地,启动代码至少需要设置一个初始堆栈指针,初始化静态数据,然后跳转到 main。在嵌入式系统上,还需要初始化各种硬件,例如 PLL、内存控制器等。
【解决方案3】:
您的第一个问题已在指南中得到解答。
当您在操作系统上加载程序时,您的 .data 部分(基本上是非零全局变量)会从“二进制”加载到内存中的正确偏移量中,这样当您的程序启动时,这些内存位置代表您的变量具有这些值。
unsigned int x=5;
unsigned int y;
作为一名 C 程序员,您编写了上述代码,并且在您第一次开始使用它时希望 x 为 5,是吗?好吧,如果从闪存、裸机启动,您没有操作系统可以为您将该值复制到 ram 中,必须有人这样做。此外,所有 .data 内容都必须在闪存中,数字 5 必须在闪存中的某个位置,以便可以将其复制到 ram。所以你需要一个闪存地址和一个内存地址。同一件事的两个地址。
这开始回答您的第二个问题,对于您编写的每一行 C 代码,您都假设例如任何函数都可以调用任何其他函数。您希望能够调用函数吗?并且您希望能够拥有局部变量,并且您希望上面的变量 x 为 5,并且您可能会假设 y 将为零,尽管幸运的是,编译器开始对此发出警告。通用 C 的启动代码至少设置了堆栈指针,它允许您调用其他函数并具有局部变量并且具有超过一两行代码长度的函数,它将 .bss 归零以便上面的 y 变量为零,它将值 5 复制到 ram 以便在运行入口点 C 函数的代码时 x 准备好运行。
如果您没有操作系统,那么您必须有代码来执行此操作,是的,有许多沙箱和工具链是为已经具有启动和链接器脚本的各种平台设置的,这样您就可以
gcc -O myprog.elf myprog.c
现在这并不意味着您可以在没有...system...printf、fopen 等的情况下进行系统调用。但是,如果您下载其中一个工具链,这确实意味着您实际上不必编写链接描述文件或引导程序。
但它仍然是有价值的信息,请注意,基于操作系统的程序也需要启动代码和链接描述文件,只是操作系统的本机编译器假定您将主要为该操作系统编写程序,因此,他们在该工具链中提供了链接器脚本和启动代码。