堆,堆栈,文本等不同的段如何与物理内存相关?

  1. 编译C程序并创建目标文件(ELF)时。 目标文件包含不同的部分,如bss,数据,文本和其他段。 我知道ELF的这些部分是虚拟内存地址空间的一部分。 我对吗? 如果我错了,请纠正我。

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

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

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

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

我很困惑,目标文件的这些不同段(如文本,rodata,数据,bss,堆栈和堆)是物理内存(RAM)的一部分,其中所有程序都被执行。 但我觉得我的理解是错误的。 当进程或程序执行时,这些不同的段如何与物理内存相关?

1.正确,ELF文件列出了操作系统应将ELF文件内容复制到的进程的虚拟地址空间中的绝对或相对位置。 (bss只是一个位置和大小,因为它应该全为零,所以不需要在ELF文件中实际存在零)。 请注意,位置可以是绝对位置(如虚拟地址0x100000或相对位置,如文本结尾后的4096字节)。

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

3.您正在使用的编程语言定义哪个未初始化状态进入bss,并且显式初始化。 请注意,bss不包含对这些变量的“引用”,它支持这些变量的存储。

4.从生成的代码中隐式引用堆栈变量。 ELF文件中没有任何关于它们(甚至是堆栈)的明确说明。

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

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

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

我不确定1,2和3是否正确,但我可以解释4和5。

4 :它们从堆栈顶部的偏移量引用。 执行函数时,堆栈顶部会增加,以便为局部变量分配空间。 编译器确定堆栈中局部变量的顺序,以便编译器指出变量从堆栈顶部的偏移量。

物理内存中的堆栈颠倒放置。 堆栈的开头通常具有最高的可用内存地址。 当程序运行并为局部变量分配空间时,堆栈顶部的地址会减少(并且可能导致堆栈溢出 – 与较低地址上的段重叠:-))

5 :使用指针 – 动态分配变量的地址存储在(本地)变量中。 这对应于在C中使用指针。

我在这里找到了很好的解释: http : //www.ualberta.ca/CNS/RESEARCH/LinuxClusters/mem.html

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

 $ size -A -x my_elf_binary 

是虚拟地址。 具有操作系统的MMU执行从虚拟地址到RAM物理地址的转换。

如果您想了解这些内容,请尽可能使用源代码(www.kernel.org)了解操作系统。
您需要意识到OS内核实际上正在运行CPU并管理内存资源。 而C代码只是一个轻量级脚本,用于驱动操作系统并仅使用寄存器运行简单操作。

  1. 虚拟内存和物理内存是关于CPU的TLB,让用户空间进程通过TLB(使用页表)硬件的function虚拟地使用连续内存。 因此,映射到连续虚拟内存的实际物理内存可以分散到RAM上的任何位置。 编译程序不知道这个TLB的东西和物理内存地址的东西。 它们在OS内核空间中进行管理。

  2. BSS是OS准备为零填充内存地址的部分,因为它们未在c / c ++源代码中初始化,因此由编译器/链接器标记为bss。

  3. 堆栈是操作系统最初只准备少量内存的东西,每次进行函数调用时,地址都会被按下,这样就有更多空间放置局部变量,并在你想要返回时弹出从function。 当第一个少量内存已满并到达底部时,新物理内存将分配给虚拟地址,并且会发生页面错误exception,并且OS内核将准备新的物理内存,用户进程可以继续工作。

  4. 没有魔法。 在目标代码中,对从malloc返回的指针所做的每个操作都被处理为从malloc函数调用返回的寄存器值的偏移量。

实际上malloc正在做很复杂的事情。 有各种实现(jemalloc / ptmalloc / dlmalloc / googlemalloc / …)用于改进动态分配,但实际上它们都是使用sbrk或mmap(/ dev / zero)从操作系统获取新的内存区域,这称为匿名内存。

只需在命令readelf上找一个人找出程序不同部分的起始地址。

关于第一个问题,你是绝对正确的。 由于今天的大多数系统都使用运行时绑定,因此只有在执行期间才能知道实际的物理地址。 而且,在编译和加载时间链接不同的库之后,编译器和加载器将程序划分为不同的段。 因此,虚拟地址。

由于运行时绑定,第二个问题是在运行时。 第三个问题是真的。 所有未初始化的全局变量和静态变量都进入BSS。 还要注意特殊情况:即使将它们初始化为0,它们也会进入BSS。

4.如果查看gcc生成的汇编程序代码,可以看到内存局部变量是通过命令push或通过更改寄存器ESP值在堆栈中分配的。 然后用命令mov或类似的东西启动它们。