Как использование метода подкачки в ОС оправдывает свои накладные расходы?

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

Я не понимаю, почему мы не можем просто разделить программу на произвольное количество сегментов каждый раз, когда мы загружаем ее в оперативную память. Мы могли бы разделить ее таким образом, чтобы каждый сегменты «заполняют дыру» в оперативной памяти, если это необходимо, и, таким образом, решают проблему внешней фрагментации. Очевидно, что программа может быть загружена непостоянным образом, и нам нужно будет хранить только 2 адреса на сегмент (верхняя и нижняя граница) и может быть, какая-то таблица сегментов, чтобы поддерживать порядок.

Процитирую книгу, которую я читаю (концепции ОС — Абрахам Зильбершатц, Питер Бэр Галвин, Грег Ганье, 9-е издание): мейнфреймов через тех, что для смартфонов».

Я что-то упустил здесь? Как использование подкачки оправдывает накладные расходы? Нам действительно нужно отслеживать каждую страницу? Принимаются ли во внимание какие-либо другие факторы при выборе правильного метода управления памятью?


person wolfofuniverse    schedule 05.05.2020    source источник
comment
И как вы будете выбирать сегменты для каждой программы? Сколько сегментов и так далее... как запустить несколько программ, сопоставленных с одними и теми же адресами? Вы меняете весь сегмент каждый раз? Страницы маленькие. Таблицы страниц кэшируются, что делает доступ очень быстрым.   -  person Tony Tannous    schedule 06.05.2020
comment
Я думал, что каждый раз, когда мы загружаем программу (или ее часть) в оперативную память, мы загружаем ее в первую доступную дыру. Если она не подходит, мы загружаем остаток в следующую доступную дыру и так далее... Так что я думаю количество сегментов будет меняться каждый раз, когда мы его загружаем. То же самое касается каждой программы, которую мы пытаемся загрузить в память. Мне просто кажется, что мы могли бы решить ту же проблему, запоминая только 2 адреса на сегмент (отверстие) отслеживание каждого кадра (меньше накладных расходов).   -  person wolfofuniverse    schedule 06.05.2020
comment
Как тогда быть с распределением памяти во время жизни программы?   -  person cassandrad    schedule 07.05.2020


Ответы (2)


Пейджинг является основой для различных «трюков с виртуальной памятью», таких как:

  • вместо того, чтобы ждать загрузки файла, прежде чем его можно будет использовать; пометить страницы как «принадлежащие файлу с отображением памяти», а затем сделать вид, что файл был загружен (и получить страницы с диска, когда они действительно нужны, возможно, с некоторой стратегией предварительной выборки, происходящей в фоновом режиме). Это снижает потребление оперативной памяти и может ускорить такие вещи, как время запуска исполняемого файла.

  • вместо выделения фактической физической ОЗУ во время таких вещей, как запуск процесса (для его «раздела .bss»/неинициализированных данных, «еще не используемого» пространства кучи и т. д.), и вместо буквального копирования всех страниц во время fork(); используйте трюки «копировать при записи», чтобы вам нужно было создавать/копировать данные только тогда, когда они фактически изменены. Это снижает потребление ОЗУ и ускоряет все (особенно, когда большая часть памяти никогда не модифицируется).

  • если оперативной памяти недостаточно, вы можете просто отправить страницы в пространство подкачки (например, на диск) и все будет работать. Это намного быстрее, чем ждать, пока произойдет что-то, что никогда не произойдет (потому что процесс рухнул из-за нехватки памяти).

  • все вышеперечисленные вещи, которые уменьшают объем фактической используемой оперативной памяти, означают, что вместо этого вы можете использовать эту оперативную память для кэшей файловой системы. Значительно большие кэши файловой системы могут означать значительно более быстрый файловый ввод-вывод (меньше «промахов файлового кэша», менее медленный дисковый ввод-вывод). Конечно, если данные находятся в файловом кеше, вы можете отображать одни и те же страницы как «копирование при записи» в несколько разных процессов (без копирования данных или использования большего количества ОЗУ).

Для накладных расходов; Производители процессоров приложили немало усилий для минимизации накладных расходов (используя «резервные буферы перевода», стратегии предварительной выборки, выполнение вне очереди, чтобы ЦП был занят, пока он ожидает перевода, и т. д.). Точно так же разработчики операционных систем также пытаются минимизировать накладные расходы (например, уменьшить количество переключений задач между процессами, пытаясь сохранить процессы на одном ядре и т. д.). Это означает, что накладные расходы довольно малы по сравнению со многими большими преимуществами.

Конечно, без подкачки вам в конечном итоге придется иметь дело с внешней фрагментацией, которая обычно приводит к трате большого количества процессорного времени на копирование больших объемов данных из одной части ОЗУ в другую для «дефрагментации» ОЗУ. Кроме того, вам понадобится что-то еще, чтобы гарантировать, что различные процессы изолированы и что любые разрешения (например, «эта область не является исполняемой») могут/могут применяться; и это «что-то еще», вероятно, добавит столько же накладных расходов, как и пейджинг сам по себе (если только вы не хотите серьезной проблемы с безопасностью). Имея это в виду, даже если вы проигнорируете преимущества пейджинга, пейджинг, скорее всего, все равно будет менее накладным, чем без пейджинга.

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

Работа с разными размерами сегментов была бы болезненной (меньше «сдвиг и маска, чтобы найти правильный индекс в таблице», а больше «выполнить линейный поиск по всем сегментам, чтобы найти правильный сегмент»). Если вы используете сегменты фиксированного размера, это будет намного быстрее; но это и есть пейджинг («страница» — это ваш «сегмент фиксированного размера»).

person Brendan    schedule 07.05.2020
comment
Будет ли справедливо сказать это, учитывая высокий коэффициент попаданий TLB (из-за локальности данных/инструкций?) и то, что вы сказали о минимизации накладных расходов, даже если бы мы могли теоретически знать (хранить\вычислять) местоположение каждой отдельной страницы программы, выполняемой (в быстрых регистрах), это не дало бы значительных преимуществ по сравнению с знанием лишь нескольких из них (как в случае с TLB) и, следовательно, в целом не оправдывает дополнительную аппаратную поддержку для этих теоретических реализаций? Учитывая тот факт, что процесс может иметь в 100 раз больше страниц в оперативной памяти, чем в TLB (если не ошибаюсь). - person wolfofuniverse; 22.06.2020

  1. Разделение программы на произвольные сегменты.

Они не могут быть совершенно произвольными; например, я мог бы сделать очень большой вектор для некоторого приложения:

#define N 10000000
int v[N];
....
for (i = 0; i < N; i++) {
    v[i] = ...
}

На самом деле компилятор хочет, чтобы v занимал последовательные ячейки памяти. Таким образом, ваш сегментатор должен знать об этих элементах; но становится хуже:

int *v;
v = malloc(sizeof(*v) * N);
for (i = 0; i < N; i++) {
   v[i] = ...;
}
  1. Обоснование накладных расходов:

Теперь вам нужно найти большой кусок физически непрерывной оперативной памяти во время выполнения, и, поскольку у вас нет механизма перемещения, у вас нет возможности перемещать ранее выделенные куски. Это проблема фрагментации; а без страничного мму решить очень сложно.

Вы можете решить эту проблему, превратив ваши скомпилированные языки в псевдоинтерпретируемые языки; но что имеет больше накладных расходов: компиляция:

a = v[i];

into:
    ld R0,R1+R2*4    ; + overhead of mmu.
or:
    mov R0, R1+R2*4
    call findseg
    ld R0, R0

В общем случае накладные расходы с точки зрения оперативной памяти составляют порядка 0,1%; для конкретного примера 10 байт для страницы размером 4 КБ на архитектуре ARM64 или AMD64. libc.so в моей системе Linux содержит около 2 МБ текста + 40 КБ данных; большинство программ используют только очень небольшое количество этого. Благодаря пейджингу только используемые биты libc должны занимать память. В системе 64G с 32 процессами одна только экономия libc перекрывает накладные расходы на таблицу страниц.

3: Отслеживание. Есть несколько способов атаковать это. Один из них — несколько размеров страницы, которые поддерживаются в большинстве архитектур. Во-вторых, ОС не обязана обеспечивать детализацию MMU. Например, в системе со страницами по 4 КБ он может настаивать на передаче памяти только кусками по 64 КБ. Таким образом, накладные расходы на управление снижаются в 16 раз, а потери детализации незначительно увеличиваются.

person mevets    schedule 07.05.2020