Ядро ОС (Linux) выделяет виртуальную память процессу, используя набор смежных блоков памяти, называемых vm_area_struct. Каждая VMA состоит из:
Ядро группирует все эти распределения в mm_struct внутри дескриптора процесса task_struct.
mm_struct содержит список всех выделенных VMA в двух структурах данных, список VMA, отсортированный по адресам, и как часть красно-черного дерева для каждого процесса. Всякий раз, когда мы вызываем системный вызов mmap(), мы обычно передаем addr=NULL, чтобы ядро предоставило нераспределенное пространство и сопоставило его с адресным пространством нашего процесса. Это красно-черное дерево (mm_rb), которое используется для более быстрого поиска незанятого пространства в адресном пространстве процесса. Я попытался изучить, как ядро выполняет этот процесс mmap, и дальнейшее чтение — это попытка написать об этом в блоге.
Это карта виртуальной памяти процесса:
Как видно, TASK_UNMAPPED_BASE = 0x40000000 используется в качестве базового адреса для всей общей памяти, отображений памяти и общих библиотек в 32-разрядной системе. Это значение будет использоваться в функции vma_unmapped_area() для начала поиска нераспределенной области. Существуют разные подходы к распределению: восходящий и сверху вниз. Я рассмотрю восходящий подход, при котором распределение начинается с TASK_UNMAPPED_BASE. В вашей системе вы можете проверить, каков порядок распределения, проанализировав 2 выхода.
Проверьте порядок загрузки библиотеки с помощью readelf:
Здесь libpthread.so должен быть отображен первым, затем libc.so, порядок может быть дополнительно подтвержден запуском strace на исполняемом файле.
Хотя libpthread загружается первым, он сопоставляется с более высоким адресом, что указывает на подход сверху вниз к распределению.
При восходящем подходе
vma → vm_start = TASK_UNMAPPED_BASE
vma → vm_end = vma → vm_start + len;
Ядро подпрограмм mmap() начинается с функции do_mmap_pgoff():
После проверки работоспособности переданного адреса и смещения, переданного пользователем, вызывается get_unmapped_area().
Это вызовет функцию get_unmapped_area(), назначенную mm_struct, или, если предоставлена файловая система, вызовет функцию get_unammaped_area из f_op( ).
В любом случае нам нужно вызвать приведенную ниже процедуру, чтобы получить ожидаемую функцию vma(), если пользователь указал адрес.
Если адрес не был указан, мы создадим запрос vm_unmapped_area_info, а затем вызовем vm_unmapped_area().
Здесь это выглядит как очень общий запрос,
low_limit =TASK_UNMAPPED_BASE, high_limit = TASK_SIZE.
Итак, первый случай: если нет памяти из зоны mmap (слово зоны не имеет отношения к зонам памяти), то помечаем адрес highest_vm_end как начало и возвращаемся.
Если есть корневой узел или последующие узлы, нам нужно найти подходящее место или дыру, соответствующую нашему запросу (len байт). Для оптимизации этого процесса ядро поддерживает переменную «rb_subtree_gap».
Вычисление rb_subtree_gap() происходит следующим образом:
Основываясь на этом методе расчета гэпа, я написал ниже несколько примеров:
- Если присутствует только корневой узел, разрыв указывает на vm_start.
- Если vma выделена по более высокому адресу, то она должна стать правым дочерним элементом корневого узла.
- Если vma выделена по более низкому адресу, она должна стать левым дочерним элементом корневого узла.
- Промежуток рассчитывается как
max(начало — предыдущее →конец,дети →rb_subtree_gap)
Итак, на основании рис. 5 и комментария, написанного в начале функции unmapped_area(), можно найти подходящее отверстие, соответствующее нашему запросу. Для этого адреса будет вызван метод mmap_region(), чтобы выделить vm_area_struct и вставить его как в список, так и в красно-черное дерево.
Я все еще изучаю это, поэтому некоторая информация может быть упрощена, чтобы я запомнил концепцию. Приветствуются любые исправления, предложения и т.д.
Использованная литература: