Как Fortran 77 выделяет переменные общего блока?
Я занимаюсь разработкой библиотеки на C, которая должна работать с C, C++ или кодом Fortran. Один из механизмов, который он использует, - перехватывать записи на страницы в сегментах стека, кучи или data/bss. В данном случае "куча" - это специальная куча, которую библиотека создает из сопоставленного файла. Я обнаружил, что моей библиотеке не удалось перехватить запись в переменную в приложении на Фортране. Переменная объявлена как
double precision u(5,I,J,K)
Где I, J и K - целочисленные параметры (т. Е. Константы). Затем код включает вас в общий блок, называемый "полями".
При отладке под GDB я обнаружил, что адрес u не попадает в диапазон ни одного из трех сегментов данных. (Следовательно, библиотека не смогла перехватить запись!) Затем я посмотрел псевдофайл /proc//maps и обнаружил, что адрес u попадает в диапазон, который система комментирует как "куча". Но как ты попал в эту "кучу"? Код Fortran 77 в этом случае не использует нестандартное ключевое слово "allocate" для выделения в куче. Кто-нибудь может мне объяснить, какие переменные Fortran 77 (в Ubuntu Linux x86-64) размещает в "куче", и как эта "куча" создается в первую очередь?
1 ответ
Я играл с массивом в общем блоке. Похоже, что сегмент.bss в Linux действительно объединен с кучей (или, по крайней мере, пространство выделяется с использованием того же 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 байтов дополнительных данных. Он только помечается как размещаемый и ничего более - данные из файла не заполняются заранее. Вы также можете сделать вывод, что 32 байта дополнительных данных заложены ранее a_
поскольку u
начинается в 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
на самом деле просто символ локальной переменной в главной функции Fortran, в то время как a_
является глобально видимым хранилищем. Вот почему вы можете по-разному называть массивы в разных подпрограммах / функциях, но при этом обращаться к одной и той же памяти, если вы поместите их в соответствующий общий блок.
Похоже, что неуклюжий VMA.bss является результатом смещения сегмента.data в файле ELF, так как.bss находится сразу после раздела.data в памяти. Сегмент.data загружается в Linux так: mmap(2)
из файла с 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 на отдельной странице.
Все, что находится после сегмента.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 или был выделен с ALLOCATE()
, Разница в том, что.bss изначально заполнен нулями, а содержимое памяти выделяется malloc
или же ALLOCATE()
осталось как есть.