我刚学习了不同的内存段,如Text、Data、Stack和Heap。我的问题是:1- 这些段之间的边界是在编译器还是操作系统中定义的?2- 编译器或操作系统如何知道哪些地址属于每个段?我们需要在任何地方定义它吗?
这个答案是从一个更专门的嵌入式系统的角度来看,而不是像Linux这样的通用计算平台。这些部分之间的边界是在哪里定义的?是在编译器还是操作系统中?既不是编译器也不是操作系统。是链接器决定内存部分的位置。编译器从源代码生成目标文件。链接器使用链接器脚本文件将目标文件定位在内存中。链接器脚本(或链接器指令)文件是项目的一部分,并标识各种内存类型(如ROM和RAM)的类型、大小和地址。链接程序使用链接器脚本文件中的信息来了解每个内存从哪里开始。然后,链接器将每种类型的内存从一个目标文件定位到适当的内存部分。例如,代码放在.text部分中,通常位于ROM中。变量放在.data或.bss部分中,位于RAM中。堆栈也放在RAM中。当链接器填充一个部分时,它会了解到该部分的大小,并且可以知道下一个部分从哪里开始。例如,.bss部分可以从.data部分结束的地方开始。堆和栈的大小可以在链接器脚本文件中指定,也可以在IDE的项目选项中指定。嵌入式系统的IDE通常在创建项目时自动提供通用链接器脚本文件。通用链接器文件适用于许多项目,因此您可能永远不需要自定义它。但是随着您进一步定制目标硬件和应用程序,您可能会发现需要自定义链接器脚本文件。例如,如果向板子中添加外部ROM或RAM,则需要将有关该内存的信息添加到链接器脚本中,以便链接器知道如何在那里定位。链接器可以生成描述每个部分在内存中定位方式的映射文件。默认情况下可能不会生成映射文件,如果您想要查看它,则可能需要打开构建选项。编译器或操作系统如何知道哪些地址属于每个部分?我不认为编译器或操作系统实际上知道这些信息,至少不能查询它们以获取信息。编译器在链接器定位内存部分之前已经完成了其工作,因此编译器不知道该信息。操作系统呢?嵌入式应用程序甚至可能不使用操作系统。操作系统只是提供服务给应用程序的一些代码。操作系统不知道也不关心内存部分的边界在哪里。所有这些信息都已经通过可执行代码烘焙到运行操作系统之时。我们应该在哪里定义它?查看链接器脚本(或链接器指令)文件并阅读链接器手册。链接器脚本是链接器的输入,提供内存的大致轮廓。链接器将所有东西定位在内存中,并确定每个部分的范围。
对于你的问题: 这些区域之间的边界如何定义?是在编译器还是操作系统中? 答案是操作系统。对于可执行代码 (.text 段)、变量 (.data 段) 以及其他程序段的布局,没有普遍通用的寻址方案。不过,程序本身的布局是根据将要执行程序的系统 (操作系统) 规范化的。 编译器或操作系统如何知道哪些地址属于每个部分?我们需要在任何地方定义它吗? 我把你的问题分为了三个问题: 关于文本 (代码) 和数据段及其限制? 文本和数据是由编译器准备的。编译器的要求是确保它们是可访问的,并将它们打包在地址空间的较低部分中。可访问的地址空间将受到硬件的限制,例如如果指令指针寄存器是32位,则文本地址空间将为4 GiB。 关于堆段和限制?它是总可用 RAM 内存吗? 在文本和数据之后,上面的区域就是堆。使用虚拟内存技术,堆可以实际上接近最大地址空间大小。 堆和栈是否具有静态大小限制? 进程地址空间中的最后一个段是栈。栈占用地址空间的末尾部分,从末尾开始向下增长。由于堆向上增长、栈向下增长,它们基本上相互限制。此外,由于这两种类型的段都可写,所以跨越边界并不总是一种违规行为,因此可能会出现缓冲区或栈溢出。现在有机制可以阻止这些情况发生。每个进程都有一个堆 (栈) 的固定限制。这个限制可以在运行时更改 (使用 brk()/sbrk())。基本上,当进程需要更多的堆空间而已经用完了分配的空间时,标准库将向操作系统发出调用。操作系统会分配一页,通常由用户库来管理程序使用。也就是说,如果程序要求1 KiB,操作系统将提供额外的4 KiB,库将提供1 KiB给程序,并为以后程序下次请求更多空间时保留3 KiB。大多数情况下,布局将是文本、数据、堆 (向上增长)、未分配空间和最终栈 (向下增长)。它们都共享同一地址空间。