#linux-kernel
#linux-ядро
Вопрос:
Когда процесс запрашивает страницы физической памяти у ядра Linux, ядро делает все возможное, чтобы предоставить блок физически смежных страниц в памяти. Мне было интересно, почему важно, чтобы страницы были ФИЗИЧЕСКИ смежными; в конце концов, ядро может скрыть этот факт, просто предоставляя страницы, которые являются ПРАКТИЧЕСКИ смежными.
Тем не менее, ядро, безусловно, изо всех сил старается предоставлять страницы, которые являются ФИЗИЧЕСКИ смежными, поэтому я пытаюсь выяснить, почему физическая смежность так важна. Я провел некоторое исследование и, изучив несколько источников, выявил следующие причины:
1) лучше использует кэш и обеспечивает меньшее среднее время доступа к памяти (GigaQuantum: я не понимаю: как?)
2) вам приходится возиться с таблицами страниц ядра, чтобы сопоставлять страницы, которые физически НЕ являются смежными (GigaQuantum: я не понимаю этого: разве каждая страница не сопоставляется отдельно? Какие манипуляции необходимо выполнить?)
3) отображение страниц, которые физически не являются смежными, приводит к большей перегрузке TLB (GigaQuantum: я не понимаю: как?)
Согласно комментариям, которые я вставил, я не совсем понимаю эти 3 причины. Ни один из моих исследовательских источников адекватно не объяснил / не оправдал эти 3 причины. Кто-нибудь может объяснить это немного подробнее?
Спасибо! Поможет мне лучше понять ядро…
Комментарии:
1. Какую функцию вы используете для запроса страниц физической памяти из ядра?
Ответ №1:
Основной ответ действительно заключается в вашем втором пункте. Обычно, когда память выделяется внутри ядра, она не отображается во время выделения — вместо этого ядро заранее отображает столько физической памяти, сколько может, используя простое линейное отображение. Во время выделения оно просто выделяет часть этой памяти для выделения — поскольку отображение не изменено, оно уже должно быть непрерывным.
Большое линейное отображение физической памяти эффективно: как потому, что для этого можно использовать большие страницы (которые занимают меньше места для записей таблицы страниц и меньше записей TLB), так и потому, что изменение таблиц страниц — медленный процесс (поэтому вы хотите избежать этого во время выделения / освобождения).
Можно запросить выделения, которые являются только логически линейными, используя vmalloc()
интерфейс, а не kmalloc()
.
В 64-разрядных системах отображение ядра может охватывать всю физическую память — в 32-разрядных системах (за исключением систем с небольшим объемом физической памяти) напрямую отображается только часть физической памяти.
Ответ №2:
На самом деле описанное вами поведение выделения памяти является общим для многих ядер ОС, и основной причиной является распределитель физических страниц ядра. Обычно ядро имеет один распределитель физических страниц, который используется для распределения страниц как для пространства ядра (включая страницы для DMA), так и для пространства пользователя. В пространстве ядра вам нужна непрерывная память, потому что это дорого (для кода в ядре) отображать страницы каждый раз, когда они вам нужны. Например, в x86_64 это совершенно бесполезно, потому что ядро может видеть все адресное пространство (в 32-битных системах виртуальное адресное пространство ограничено 4G, поэтому обычно верхний 1G выделяется для ядра, а нижний 3G — для пользовательского пространства).
Ядро Linux использует алгоритм buddy для выделения страниц, так что выделение большего фрагмента занимает меньше итераций, чем выделение меньшего фрагмента (ну, меньшие фрагменты получаются путем разделения больших фрагментов). Более того, использование одного распределителя как для пространства ядра, так и для пространства пользователя позволяет ядру уменьшить фрагментацию. Представьте, что вы выделяете страницы для пользовательского пространства по 1 странице за итерацию. Если пользовательскому пространству требуется N страниц, вы выполняете N итераций. Что произойдет, если тогда ядру потребуется некоторая непрерывная память? Как оно может создать достаточно большой непрерывный блок, если вы украли по 1 странице из каждого большого блока и разместили их в пространстве пользователя?
[обновить]
На самом деле ядро выделяет непрерывные блоки памяти для пользовательского пространства не так часто, как вы могли бы подумать. Конечно, оно выделяет их, когда создает ELF-образ файла, когда создает readahead, когда пользовательский процесс считывает файл, оно создает их для операций IPC (канал, буферы сокетов) или когда пользователь передает флаг MAP_POPULATE системному вызову mmap. Но обычно ядро использует «ленивую» схему загрузки страниц. Это предоставляет непрерывное пространство виртуальной памяти для пользовательского пространства (когда пользователь впервые выполняет malloc или mmap), но это не заполняет пространство физическими страницами. Оно выделяет страницы только при возникновении ошибки страницы. То же самое верно, когда пользовательский процесс выполняет форк. В этом случае дочерний процесс будет иметь адресное пространство «только для чтения». Когда дочерний элемент изменяет некоторые данные, возникает ошибка страницы, и ядро заменяет страницу в дочернем адресном пространстве новой (так что родительский элемент и дочерний элемент теперь имеют разные страницы). Обычно в таких случаях ядро выделяет только одну страницу.
Конечно, существует большой вопрос фрагментации памяти. Пространство ядра всегда нуждается в непрерывной памяти. Если бы ядро выделяло страницы для пользовательского пространства из «случайных» физических расположений, было бы намного сложнее получить большой объем непрерывной памяти в ядре через некоторое время (например, после недели безотказной работы системы). В этом случае память была бы слишком фрагментирована.
Для решения этой проблемы ядро использует схему «readahead». Когда в адресном пространстве какого-либо процесса возникает ошибка страницы, ядро выделяет и сопоставляет более одной страницы (поскольку существует вероятность, что процесс прочитает / запишет данные со следующей страницы). И, конечно, в этом случае он использует физически непрерывный блок памяти (если это возможно). Просто для уменьшения потенциальной фрагментации.
Ответ №3:
Пара из тех, о которых я могу вспомнить:
- Аппаратное обеспечение DMA часто обращается к памяти в терминах физических адресов. Если у вас есть данные на несколько страниц для передачи с аппаратного обеспечения, вам понадобится непрерывный кусок физической памяти для этого. Некоторые старые контроллеры DMA даже требуют, чтобы эта память располагалась по низким физическим адресам.
- Это позволяет ОС использовать большие страницы. Некоторые модули управления памятью позволяют вам использовать больший размер страницы в записях вашей таблицы страниц. Это позволяет использовать меньше записей таблицы страниц (и слотов TLB) для доступа к тому же объему виртуальной памяти. Это снижает вероятность пропуска TLB. Конечно, если вы хотите выделить страницу размером 4 МБ, вам понадобится 4 МБ непрерывной физической памяти для ее резервного копирования.
- Ввод-вывод с отображением в памяти. Некоторые устройства могут быть сопоставлены с диапазонами ввода-вывода, для которых требуется непрерывный диапазон памяти, охватывающий несколько кадров.
Ответ №4:
Запрос ядра на непрерывное или несмежное выделение памяти зависит от вашего приложения.
Например, о непрерывном распределении памяти: если вам требуется выполнить операцию DMA, то вы будете запрашивать непрерывную память через вызов kmalloc(), поскольку для операции DMA требуется память, которая также является физически непрерывной, поскольку в DMA вы укажете только начальный адрес блока памяти, а другое устройство будет выполнять чтение или запись из этого местоположения.
Некоторые операции не требуют непрерывной памяти, поэтому вы можете запросить фрагмент памяти через vmalloc(), который выдает указатель на незаразную физическую память.
Таким образом, это полностью зависит от приложения, которое запрашивает память.
Пожалуйста, помните, что хорошей практикой является то, что если вы запрашиваете непрерывную память, то это должно основываться только на потребностях, поскольку ядро изо всех сил старается выделить физически непрерывную память.Ну, у kmalloc() и vmalloc() также есть свои ограничения.
Ответ №5:
-
Размещение того, что мы собираемся много читать, физически близко друг к другу, использует пространственную локальность, то, что нам нужно, с большей вероятностью будет кэшироваться.
-
Не уверен насчет этого
-
Я полагаю, это означает, что если страницы не являются смежными, TLB должен проделать больше работы, чтобы выяснить, где они все находятся. Если они смежные, мы можем выразить все страницы для процесса как PAGES_START PAGE_OFFSET . Если это не так, нам нужно сохранить отдельный индекс для всех страниц данного процесса. Поскольку TLB имеет конечный размер и нам нужно получить доступ к большему количеству данных, это означает, что мы будем намного чаще обмениваться данными.
Комментарии:
1. » использует пространственную локальность » можете ли вы объяснить это?
Ответ №6:
ядру не нужны физически смежные страницы, на самом деле ему просто нужны эффективность и стабильность. монолитное ядро, как правило, имеет одну таблицу страниц для пространства ядра, разделяемого между процессами, и не хочет, чтобы в пространстве ядра возникали ошибки страниц, которые делают дизайн ядра слишком сложным
таким образом, обычные реализации на 32-разрядной архитектуре всегда разделяют адресное пространство 3g / 1g на адресное пространство 4g для пространства ядра 1g, обычные сопоставления кода и данных не должны генерировать рекурсивные ошибки страниц, которые слишком сложны для управления: вам нужно найти пустые фреймы страниц, создать сопоставление в mmu и обработать tlb flush для новых сопоставлений при каждой ошибке страницы на стороне ядра ядро уже занято устранением ошибок страницы на стороне пользователя
кроме того, линейное отображение 1: 1 может содержать гораздо меньше записей в таблице страниц, поскольку оно может использовать больший размер страницы (> 4 кб). меньшее количество записей приводит к меньшему количеству пропусков tlb.
таким образом, распределитель buddy в линейном адресном пространстве ядра всегда предоставляет физически смежные фреймы страниц, даже большинству кодов не нужны смежные фреймы, но многие драйверы устройств, которым нужны смежные фреймы страниц, уже считают, что выделенные буферы через общий распределитель ядра физически непрерывны