Как sbrk/brk реализован в Linux?
Я думал о том, как ядро Linux реализует системные вызовы, и мне было интересно, может ли кто-нибудь дать мне общее представление о том, как работает sbrk / brk?
Я просмотрел код ядра, но его так много, и я его не понимаю. Я надеялся на резюме от кого-то?
4 ответа
На очень высоком уровне ядро Linux отслеживает память, видимую для процесса, как несколько "областей памяти" (struct vm_area_struct
). Существует также структура, которая представляет (опять же на очень высоком уровне) все адресное пространство процесса (struct mm_struct
). Каждый процесс (кроме некоторых потоков ядра) имеет ровно один struct mm_struct
что, в свою очередь, указывает на все struct vm_area_struct
для памяти, к которой он может получить доступ.
sys_brk
системный вызов (находится в mm/mmap.c
) просто настраивает некоторые из этих областей памяти. (sbrk
это фантик вокруг brk
). Это делается путем сравнения старого значения brk
адрес (находится внутри struct mm_struct
) и запрошенное значение.
Было бы проще взглянуть на mmap
семейство функций во-первых, так как brk
это частный случай этого.
Вы должны понимать, как работает виртуальная память, и как отображение MMU связано с реальной оперативной памятью.
Реальная оперативная память делится на страницы, традиционно 4 КБ каждая. каждый процесс имеет свое собственное отображение MMU, которое представляет этому процессу линейное пространство памяти (4 ГБ в 32-разрядной версии Linux). конечно, не все они на самом деле выделены. сначала он почти пустой, то есть ни одна реальная страница не связана с большинством адресов.
когда процесс достигает нераспределенного адреса (либо пытается прочитать, записать или выполнить его), MMU генерирует ошибку (аналогично прерыванию), и вызывается система VM. Если он решает, что какая-то ОЗУ должна быть там, он выбирает неиспользуемую страницу ОЗУ и связывается с этим диапазоном адресов.
таким образом, ядру все равно, как процесс использует память, а процессу на самом деле все равно, сколько в нем оперативной памяти, оно всегда будет иметь одинаковые линейные 4 ГБ адресного пространства.
теперь brk/sbrk
работать на немного более высоком уровне: в принципе любой адрес памяти "за пределами" этой метки недопустим и не получит страницу ОЗУ, если к ней обращаются, вместо этого процесс будет уничтожен. библиотека userspace управляет выделением памяти в пределах этого предела и только при необходимости просит ядро увеличить его.
Но даже если процесс начался с установки brk
до максимально допустимого, он не будет выделять реальные страницы ОЗУ, пока не начнет получать доступ ко всем этим адресам памяти.
Что ж, с точки зрения сверхвысокого уровня ядро выделяет блок памяти с возможностью постраничного вывода, модифицирует таблицы страниц процесса, запрашивающего этот блок, так, чтобы память отображалась в пространстве VA процесса, а затем возвращает адрес.
Ключевой концепцией того, как ядро Linux передает память пользовательскому процессу, является то, что доступная куча процессов (сегмент данных) растет снизу вверх. ядро не отслеживает отдельные фрагменты памяти, только непрерывный блок памяти. Системные вызовы brk / sbrk увеличивают объем памяти, который есть у процесса, но процесс должен управлять им в виде полезных частей.
Ключевым следствием этого является то, что память, разбросанная по неиспользуемому адресному пространству процессов, не может быть возвращена операционной системе для других целей. Только память в самом конце сегмента данных может быть возвращена операционной системе, поэтому используемая память ближе к концу должна быть смещена вниз к вершине. На практике почти нет распределителей сделать это. По этой причине обычно важно хорошо справиться с управлением максимальным объемом памяти, который использует процесс, потому что это определяет, сколько памяти останется для других процессов.