Комментарии 24

Хочу добавить несколько замечаний:


  1. Резервировать сразу огромное количество виртуальной памяти — достаточно плохая практика, потому что у нас сразу появляются расходы на хранение таблицы страниц: 32 бит под каждую страницу.


  2. Термин "файл подкачки" сильно коробит слух, потому что доступная физическая память тоже является частью этого файла подкачки. А сам своп-файл на диске при этом может и отсутствовать.


Резервировать сразу огромное количество виртуальной памяти — достаточно плохая практика, потому что у нас сразу появляются расходы на хранение таблицы страниц: 32 бит под каждую страницу.

Не могу говорить о всех операционных системах на свете, но к Windows ваши слова точно не применимы.


Во-первых, похоже, что вы путаете резервирование (MEM_RESERVE) и выделение (MEM_COMMIT) страниц. Прочитайте статью об устройстве виртуальной памяти в Windows, которую tyomitch написал в далёком 2006 году. Если не хотите читать, то коротко: разница между резервированием и выделением страниц такая же, как между бронированием номеров в гостинице и заселением в них. Вы не сможете создать дефицит чистого постельного белья в городе, даже если забронируете все номера всех гостиниц в нём (но не станете никем из заселять).


Во-вторых, даже если вы не резервируете, а именно выделяете какое-то количество страниц с помощью VirtualAlloc(), последняя лишь создаёт (или обновляет) структуру MMVAD_SHORT, описывающую диапазон страниц, но совершенно никак не обновляет таблицы страниц, не инициализируя новые PDE или PTE.


Инициализация/создание новых PDE/PTE происходит лишь при первом обращении к соответствующей странице. Когда происходит первый page fault при попытке доступа к ней. То есть каталог страниц обновляется только по мере необходимости (on demand).


Не верите: можете раздобыть утёкшие исходники ядра и прочитать от корки до корки исходники ядерной функции NtAllocateVirtualMemory (VirtualAlloc лишь переходник к ней). Единственным исключением является вызов VirtualAlloc с флагом MEM_PHYSICAL.

что вы путаете резервирование (MEM_RESERVE) и выделение (MEM_COMMIT) страниц.

Нет, не путаю. MEM_RESERVE — это не резервирование памяти, а резервирование адресного пространства. Обращаться к памяти, помеченной как MEM_RESERVE, нельзя.


Во-вторых, даже если вы не резервируете, а именно выделяете какое-то количество страниц с помощью VirtualAlloc(), последняя лишь создаёт (или обновляет) структуру MMVAD_SHORT, описывающую диапазон страниц, но совершенно никак не обновляет таблицы страниц, не инициализируя новые PDE или PTE.

Понятно. В любом случае, в Windows нет overcommit, поэтому выделение большого количества памяти не имеет смысла.

Нет, не путаю. MEM_RESERVE — это не резервирование памяти, а резервирование адресного пространства.

Это какая-то терминологическая демагогия пошла. Общепринято, что более гибкий и многозначный термин «память» используют часто как синоним понятия «адресное пространство».

В любом случае, вы написали
Резервировать сразу огромное количество виртуальной памяти — достаточно плохая практика, потому что у нас сразу появляются расходы

Можете сделать процесс, который резервирует 1 Гб (или вообще все свободные регионы) своего адресного пространства (после чего засыпает или зацикливается), и запустить 200 таких процессов. Никакие системные ресурсы не исчерпаются. Будет потрачено 200 фрагментов неподкачиваемого пула ядра на структуры MMVAD_SHORT. Ровно столько же было бы потрачено, если бы процесс закоммитил 200 несмежных 4-килобайтных страниц (800 кб в общей сумме).

1) выделять виртуальную память на каждый чих не очень хорошо. Так как в ядре должны вестись списки выделенных/занятых страниц. И даже если они хорошо оптимизированны, все равно расходы на их поддержание, поиск свободного места, и т.д. не нулевые.

2) eop_malloc() — вы тут забыли про выравнивание. malloc() возвращает выравненный указаталь, который подходит для текущей системы. У вас же нет никаких гарантий. В итоге очень можно на эксепшин какой-нибудь напороться.
Хорошо, кроме того, что автор не владеет терминологией и подменяет виртуальную память одним из её опциональных свойств — возможностью подкачки (пейджингом).
Поучительно, спасибо. Вспоминаются времена, когда для быстрой обработки результатов эксперимента был впервые куплен огромный объём ОЗУ — 2 МБ. Почти столько же занимал массив экспериментальных данных, программист извернулся и нашёл таки способ обработки массива на месте, без необходимости выделения памяти под промежуточные результаты.

«Операционная система обычно не позволяет выделять все возможное адресное пространство. Например, в 64-битной Windows мы можем выделить только 256 ТБ виртуальной памяти.»
А вот это уже было и не выглядит дальновидным. 32-разрядная Windows могла адресовать от 2,5 до 3,5 ГБ ОЗУ в зависимости от BIOS. Тогда тоже казалось, что 4 ГБ это недостижимо огромное значение, а сейчас открыть пару закладок без 16 ГБ может быть проблематично.

Причём это ограничение не на физическую память, а на виртуальную. Т.е. каждый процесс может выделить по 256 ТБ. Правда, особого смысла в этом нет: одна только таблица страниц займёт 256 ГБ, если страницы будут по 4КБ.


С другой стороны, замаппить целиком в память образ жёсткого диска — почему бы и нет?

Ну 256Гб — это в пределе. До первого обращения к странице, т.е. до page fault, записи в таблице страниц не появится.

Зависит от реализации. В Linux, например, все выделенная память сразу засовывается в таблицу страниц и помечается как CoW. Сэкономить получится только при использовании больших страниц. Ну а в Windows такая возможность просто отсутствует: нельзя выделить память, которая не подкреплена памятью или файлом, можно только зарезервировать адресное пространство без автоматического выделения.

В линуксе вызов mmap создает новую запись VMA, но это не приводит к добавлению всех страниц из выделенного диапазона виртуальной памяти в таблицу mmu. Такая запись появляется только после page fault. Ну т.е. mmap в этом смысле дешев. Про windows не знаю

Да, действительно. После


mmap(nullptr, 64L * 1024L * 1024L * 1024L * 1024L, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, 0, 0)

ничего страшного не произошло.


С другой стороны, если эту память пытаться начать читать (но не писать), то сразу становится видно, как память начинает понемногу утекать, причём утёкшая память никак не отображается в информации о процессе.


А про приколы с таблицами страниц ещё эта статья вспомнилась:
https://habr.com/ru/post/421153/

А откуда взялось число 256Гб? Маппинг страниц описан вот тут: www.iaik.tugraz.at/teaching/materials/os/tutorials/paging-on-intel-x86-64
Структура представляет собой дерево, размер узла дерева — 8 байт. Для того, чтобы покрыть 48 бит адресного пространства целиком нужно 2^36 * 8 байт = 512Гб памяти на самом нижнем уровне. И еще немного по памяти на 3 уровнях выше — в 512 раз меньше на каждом.
32-разрядная Windows могла адресовать от 2,5 до 3,5 ГБ ОЗУ

Неверно. С использованием PAE можно адресовать до 64 ГБ физической памяти в 32-битном режиме, начиная уже с Windows XP. Собственно, 32-битные серверные версии Windows поддерживали много памяти. Ограничивалось только виртуальное пространство для одного процесса.


Проблемы же были в 32-битных драйверах, которые использовали прямой доступ к памяти, и которые продолжали использовать 32-битные указатели вместо 64-битных. Или которым не нравилось, что PCI hole почему-то находится посреди физического адресного пространства.

Про PAE знаю, хотя использовать не приходилось. Всегда полагал, что работает PAE на серверной Windows, хотя формально поддерживается в XP. О том же говорит следующая цитата:

"… для обеспечения совместимости с плохо написанными драйверами функциональность PAE в SP2 для Windows XP была обрезана. И хотя сам этот режим существует и, более того, на компьютерах с современными процессорами включается по умолчанию, никакого расширения адресного пространства он не дает, просто передавая на выход те же адреса, которые были поданы на вход. Фактически система ведет себя как обычная 32-разрядная без PAE.

То же самое поведение было унаследовано Windows Vista, а затем перешло к Windows 7 и будущей Windows 8. Конечно, 32-разрядным. Причина, по которой это поведение не изменилось, осталась той же самой: обеспечение совместимости. Тем более что необходимость выгадывать доли гигабайта отпала: те, кому нужны большие объемы памяти, могут использовать 64-разрядные версии ОС."

Но вот интересно, если PAE позволяет каждому процессу использовать до 4 ГБ ОЗУ, то на материнской плате с корявым BIOS, где 32-битной Windows доступно лишь 2,5 ГБ, каков в этом случае будет адресуемый каждым процессом максимум ОЗУ?
Каков в этом случае будет адресуемый каждым процессом максимум ОЗУ?

Количество адресуемой виртуальной памяти не связано с объёмом физической памяти.


Каждому процессу доступно 4 ГБ виртуальной памяти, из которых 2 ГБ отводится пользовательскому коду, а 2 ГБ — системным библиотекам. Возможен запуск системы с ключом /3GB, тогда соотношение меняется: 3 ГБ — пользовательскому коду и 1 ГБ — системным библиотекам. Правда, некоторый софт с кривой знаковой адресной арифметикой от такого сходит с ума, поэтому использование этого ключика не получило особого распространения.


Тем более что необходимость выгадывать доли гигабайта отпала: те, кому нужны большие объемы памяти, могут использовать 64-разрядные версии ОС."

Что интересно, в 64-битном режиме проблема частично сохранилась. Например, при работе с устройствами, которые умеют только в 32-битное DMA.

С использованием PAE можно адресовать до 64 ГБ физической памяти в 32-битном режиме

Ох уж этот PAE, включение которого очень часто приводило к синему экрану с сообщением об ошибке IRQL_NOT_LESS_OR_EQUAL по причине просто фантастического количества драйверов (в том числе от компаний, от которых такого подвоха не ожидал), не совместимых с PAE (вроде, если память не изменяет, связано с возможностью перемещения кода драйвера за границу физической памяти 4G).

вроде, если память не изменяет, связано с возможностью перемещения кода драйвера за границу физической памяти 4G

Это как раз не проблема, т.к. здесь используется виртуальная память, и логические адреса все равно остаются 32-битными. Проблемы же возникают, когда драйверу нужно работать с физической памятью для обмена данными между памятью и железкой.


Кстати, с 32-битными десктопными системами вполне можно было заюзать всю память через рамдиск и включение свопа на него.

Да, я тоже думал что так можно. Но для этого нужно включить режим PAE что автоматом делает недоступной гибернацию.

В тексте речь о страницах (12 бит), а в примерах кода какие-то десятичные числа.
Может, гигабайт всё же уместнее определить как 1024 * 1024 * 1024, а не 1000000000?

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Информация

Дата основания
Местоположение
Россия
Сайт
otus.ru
Численность
51–100 человек
Дата регистрации

Блог на Хабре