我在一个公共块中玩弄了一个数组。看起来,Linux 中的 .bss 段确实与堆合并(或者至少使用相同的brk(2)
机制分配空间)。
这是相关的 Fortran 代码:
double precision u(5,20,20,20)
common /a/ u
产生的 GNU 汇编指令gfortran
是:
.comm a_,320000,32
这声明了一个名为a_
320000 字节大的通用符号,并且应该在 32 字节边界上对齐。当链接器看到此声明并且没有其他定义a_
为它在 .bss 中为其保留空间时,objdump
在生成的二进制文件上运行时可以清楚地看到:
Sections:
Idx Name Size VMA LMA File off Algn
...
22 .data 00000010 0000000000600b40 0000000000600b40 00000b40 2**3
CONTENTS, ALLOC, LOAD, DATA
23 .bss 0004e220 0000000000600b60 0000000000600b60 00000b50 2**5
ALLOC
...
这里的 .bss 是 320000 (0x4e200) 字节加上一些 32 字节的附加数据。它仅被标记为可分配的,仅此而已 - 没有从文件中预填充数据。您还可以推断,从 VMA 0x600b80 开始,a_
之前放置了 32 个字节的附加数据:u
(gdb) info address u
Symbol "u" is static storage at address 0x600b80.
(gdb) info symbol &u
a_ in section .bss of /path/to/a.out
u
实际上只是Fortran主函数中局部变量的符号,a_
而是全局可见的存储。这就是为什么您可以在不同的子例程/函数中以不同的方式命名数组,但如果将它们放在适当的公共块中,仍然可以访问相同的内存。
看起来 .bss 的笨拙 VMA 是 ELF 文件中 .data 段偏移的结果,因为 .bss 位于内存中 .data 段之后。在 Linux 中加载 .data 段的方式是,它是mmap(2)
从文件中 -ed 的,MAP_PRIVATE
它为映射提供了写时复制语义:
00400000-00401000 r-xp 00000000 00:1d 25681168 /path/to/a.out
00600000-00601000 rw-p 00000000 00:1d 25681168 /path/to/a.out <-- .data
00601000-00670000 rw-p 00000000 00:00 0 [heap]
.bss 与 .data 映射在同一页面中开始,这是有道理的,因为两者都保存读/写数据并且预计会被写入,并且通过不在单独的页面上启动 .bss 可以节省一些 VM。
.data 段之后的所有内容都没有文件映射备份,因此属于可见的动态可调整空间,如[heap]
. 这个空间以及堆本身是通过移动数据段的末尾来控制的,/proc/pid/maps
即所谓的程序中断brk(2)
。内核中的 ELF 加载程序最初将程序中断移动得足够远,以便为 .bss 保留空间,如strace
可执行文件所示:
execve("./a.out", ["a.out"], [/* 230 vars */]) = 0
brk(0) = 0x64f000 <-- already moved past the .bss
我们知道 .data 段从 00600000 开始。.bss 从 00600B60 开始。公共块分配在 0x600b80,它的大小是 0x4e200,所以它在 0x64ed80 结束,四舍五入到页面边界得到 0x64f000。如果没有其他动态链接库自行分配空间,则真正的程序堆将在此处开始。
由于动态内存分配器malloc(3)
使用相同的brk(2)
机制(或匿名mmap(2)
用于大型分配或数据段大小的限制已用完),因此数组是在 .bss 中还是使用 .bss 分配实际上并不重要ALLOCATE()
。不同之处在于 .bss 最初用零填充,而由malloc
or分配的内存内容ALLOCATE()
保持原样。