I played around with an array in a common block. What it looks like is that the .bss segment in Linux is really merged with the heap (or at least space is allocated using the same brk(2)
mechanism).
Here is the relevant Fortran code:
double precision u(5,20,20,20)
common /a/ u
The GNU assembly directive that gfortran
produces is:
.comm a_,320000,32
That declares a common symbol named a_
that is 320000 bytes large and should be aligned on a 32-byte boundary. When the linker sees this declaration and no other definitions of a_
it reserves space for it in .bss as is clearly visible from running objdump
on the resulting binary:
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
...
Here the .bss is 320000 (0x4e200) bytes plus some 32 bytes of additional data. It is only marked as allocatable and nothing more - no data is prepopulated from the file. You can also deduce that the 32 bytes of additional data is laid before a_
since u
starts at VMA 0x600b80:
(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
is really just the symbol of a local variable in the Fortran main function, while a_
is the globally visible storage. That's why you can name arrays differently in different subroutines/functions but still access the same memory if you put them in the appropriate common block.
It looks like the awkward VMA of .bss is a result of the offset of the .data segment in the ELF file as the .bss is located immediately after the .data section in memory. The way .data segment is loaded in Linux is that it is mmap(2)
-ed from the file with MAP_PRIVATE
which gives the mapping a copy-on-write semantics:
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 starts in the same page as the .data mapping which makes sense since both hold read/write data and are expected to be written to and a bit of VM is conserved by not starting .bss on a separate page.
Everything after the .data segment is not backed up by a file mapping and thus falls in the dynamically adjustable space that is visible as [heap]
in /proc/pid/maps
. This space as well the heap itself is controlled by moving the end of the data segment, the so-called program break, with brk(2)
. The ELF loader in the kernel initially moves the program break far enough to reserve space for the .bss as can be seen by strace
of the executable:
execve("./a.out", ["a.out"], [/* 230 vars */]) = 0
brk(0) = 0x64f000 <-- already moved past the .bss
We know that the .data segment starts at 00600000. The .bss starts at 00600B60. The common block is allocated at 0x600b80 and its size is 0x4e200 and so it ends at 0x64ed80, which rounded up to a page boundary gives 0x64f000. Here the real program heap would begin if no other dynamically linked libraries allocate space on their own.
Since the dynamic memory allocator malloc(3)
uses the same brk(2)
mechanism (or anonymous mmap(2)
for large allocations or when the limit of the data segment size was exhausted) it really doesn't matter if the array was in .bss or was allocated with ALLOCATE()
. The difference is that the .bss is filled with zeros initially while the content of memory allocated by malloc
or ALLOCATE()
is left as is.