不同的段(如堆、栈、文本)与物理内存有什么关系?

22
  1. 当C程序被编译并创建目标文件(ELF)时,目标文件包含不同的段,例如bss、data、text和其他片段。我理解这些ELF的节是虚拟内存地址空间的一部分。如果我理解有误,请纠正我。

  2. 此外,编译后的程序将与虚拟内存和页表相关联。页表将ELF中存在的虚拟内存地址与加载程序时的实际物理内存地址关联起来。我的理解正确吗?

  3. 我读到在创建的ELF文件中,bss部分只保留未初始化全局变量的引用。这里的未初始化全局变量是指在声明时未初始化的变量?

  4. 另外,我读到局部变量将在运行时(即堆栈中)分配空间。那么它们在目标文件中如何被引用?

  5. 如果程序中有特定的代码段可用于动态分配内存,那么这些变量将如何在目标文件中被引用?

我对目标文件的这些不同段(如text、rodata、data、bss、stack和heap)是否都是物理内存(RAM)的一部分,即所有程序执行的地方感到困惑。

但是我感到我的理解是错误的。当一个进程或程序正在执行时,这些不同的段与物理内存的关系如何?

6个回答

21

1. 正确的,ELF文件在进程虚拟地址空间中的绝对或相对位置上布置了操作系统应该将ELF文件内容复制到的位置。(bss仅是一个位置和大小,因为它应该全部为零,所以没有必要实际在ELF文件中包含这些零)。请注意,位置可以是绝对位置(如虚拟地址0x100000)或相对位置(如文本结束后4096字节的位置)。

2. 虚拟内存定义(保存在页表中并将虚拟地址映射到物理地址)与编译程序无关,而与代表该程序运行实例的“进程”(或“任务”或其他您的操作系统称之为的东西)相关联。例如,单个ELF文件可以加载到两个不同的进程中,位于不同的虚拟地址上(如果ELF文件是可重定位的)。

3. 您正在使用的编程语言定义了未初始化状态放在bss中的哪里,以及哪些被明确初始化。请注意,bss不包含对这些变量的“引用”,它是支持这些变量的存储。

4. 堆栈变量是从生成的代码中隐式引用的。在ELF文件中,它们(甚至堆栈本身)都没有明确的描述。

5. 像堆栈引用一样,在ELF文件中生成的代码中隐含了对堆引用。 (它们都存储在通过调用sbrk或其等效物创建的内存中,从而改变虚拟地址空间。)

ELF文件向操作系统解释如何为程序实例设置虚拟地址空间。不同的节描述不同的需求。例如,“.rodata”表示我想存储只读数据(而不是可执行代码)。“.text”节表示可执行代码。“bss”是用于存储由操作系统清零的状态的区域。虚拟地址空间意味着程序可以(可选地)依赖于启动时期望的东西在其所在的位置上。(例如,如果它要求.bss位于0x4000地址,则操作系统将拒绝启动它,或者它将出现在此处。)

请注意,这些虚拟地址由操作系统管理的页面表映射到物理地址。ELF文件的实例不需要知道任何涉及使用哪些物理页面的详细信息。


5
我不确定1、2和3是否正确,但我可以解释4和5。
4: 它们是通过栈顶的偏移量引用的。在执行函数时,栈顶会增加以分配本地变量的空间。编译器确定了栈中本地变量的顺序,因此编译器知道变量相对于栈顶的偏移量。
物理内存中的堆栈是倒置的。堆栈的开头通常具有最高可用内存地址。随着程序运行并为本地变量分配空间,堆栈顶部的地址会递减(并且可能导致堆栈溢出 - 与低地址上的段重叠 :-))
5: 使用指针 - 动态分配变量的地址存储在(本地)变量中。这对应于在C中使用指针。
我在这里找到了很好的解释:http://www.ualberta.ca/CNS/RESEARCH/LinuxClusters/mem.html

2

只需在命令readelf上运行man,即可查找程序不同段的起始地址。

关于第一个问题,您是完全正确的。由于大多数现代系统使用运行时绑定,因此只有在执行期间才会知道实际物理地址。此外,在编译和加载期间,编译器和加载器将程序链接到不同的库后将其分成不同的段。因此,使用虚拟地址。

至于第二个问题,是由于运行时绑定而发生的。第三个问题是正确的。所有未初始化的全局变量和静态变量都放在BSS中。还要注意一种特殊情况:即使它们被初始化为0,它们也会放在BSS中。


2

当您使用 size 命令检查 ELF 文件时,您会看到不同部分(.text、.bss、.data 等)的所有地址:

$ size -A -x my_elf_binary

虚拟地址是指计算机中的一种地址形式。操作系统和内存管理单元(MMU)会将虚拟地址转换为实际的物理内存地址。在这个过程中,保留了HTML标签,但不提供解释。

2
如果你想了解这些内容,需要学习操作系统,并尽可能获取源代码(www.kernel.org)。
你需要意识到操作系统内核实际上在运行CPU并管理内存资源。而C代码只是一个轻量级脚本,用于驱动操作系统并仅执行寄存器的简单操作。
虚拟内存和物理内存是关于CPU的TLB允许用户空间进程通过TLB(使用页表)硬件以虚拟方式使用连续内存的内容。因此,映射到连续虚拟内存的实际物理内存可以分散到RAM上的任何位置。编译后的程序不知道这个TLB和物理内存地址的东西,它们在操作系统内核空间中进行管理。
BSS是操作系统准备的零填充内存地址部分,因为它们在c/c++源代码中没有被初始化,因此被编译器/链接器标记为bss。
栈是操作系统仅首先准备一小部分内存,并且每次调用函数时,地址将被推下去,以便有更多的空间放置局部变量,并在想要从函数返回时弹出。当第一个小内存满且到达底部时,将为虚拟地址分配新的物理内存,并发生页面错误异常,操作系统内核将准备新的物理内存,用户进程可以继续工作。
在对象代码中,从malloc返回的指针执行的每个操作都被处理为从malloc函数调用返回的寄存器值的偏移量,没有什么神奇的东西。实际上,malloc正在执行相当复杂的事情。有各种实现(jemalloc/ptmalloc/dlmalloc/googlemalloc/...)来改善动态分配,但实际上它们都使用sbrk或mmap(/dev/zero)从操作系统获取新的内存区域,这被称为匿名内存。

1

4. 如果你查看由gcc生成的汇编代码,你会发现内存局部变量是通过push命令或改变寄存器ESP的值来分配到栈中的。然后它们会被用mov命令或类似的方式进行初始化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接