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

时间:2012-02-10 10:13:18

标签: c memory-management stack elf

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

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

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

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

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

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

6 个答案:

答案 0 :(得分:19)

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 :(得分:3)

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

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

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

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

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

答案 2 :(得分:2)

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

$ size -A -x my_elf_binary

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

答案 3 :(得分:2)

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

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

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

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

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

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

答案 4 :(得分:2)

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

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

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

答案 5 :(得分:1)

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