Для хранения образов модифицированных страниц система должна выделить
какое-то пространство на диске. Для этого может использоваться как раздел
диска, так и файл, место для которого выделяется наравне с файлами данных.
Большинство современных систем может использовать как тот, так и другой
методы, и поддерживает динамическое подключение и отключение своп-файлов
и своп-разделов.
При наличии в системе нескольких дисков рекомендуется разделить своп-пространство
между всеми (или хотя бы между менее используемыми) дисками. Дело в том,
что операции чтения и записи требуют времени на позиционирование считывающей
головки. Пока один накопитель передвигает головку, второй вполне может
передавать данные. Понятно, что линейного роста производительности с увеличением
числа дисков таким способом не получить: всегда есть вероятность, что
все требуемые данные окажутся на одном диске, да еще и находятся в разных
местах, но все равно выигрыш в большинстве случаев оказывается значительным.
В те времена, когда компьютеры были большими, для свопа часто использовались
не диски, а специальные устройства, магнитные барабаны. В отличие от диска,
который имеет одну или, реже, несколько головок чтения-записи, барабан
Рис. 5.23. Магнитный диск и магнитный барабан
В своп-файл попадают только страницы, которые изменились с момента загрузки
процесса. Если ОС использует абсолютную загрузку или позиии-онно-независимып
код, исполняющийся код не отличается от своего образа в загрузочном файле,
поэтому страницы кода вполне можно подкачивать оттуда, и нет никакой необходимости
копировать их в своп. Часто при загрузке программы система помещает в
память только страницу, на которую указывает стартовый адрес, а весь остальной
используемый код и данные подгружаются механизмом страничного обмена.
При загрузке статически инициализированных данных обычно используется
стратегия copy-on-write (копирование при модификации): первоначально страница
подкачивается из файла. Если она не будет модифицирована и ее объявят
жертвой, то при повторном обращении ее снова подгрузят из того же файла
(рис. 5.24). Только если страница будет изменена, ей выделят место в своп-файле.
Рис. 5.24. Копирование при модификации
Если же используется относительная загрузка или та или иная форма сборки в момент загрузки (разделяемые библиотеки или DLL), при загрузке кода происходит перенастройка адресов. В этом случае возможны два подхода: копировать перенастроенный код в своп или производить перенастройку заново после каждого страничного отказа.
Отображение файлов в память в Unix
Системы семейства Unix предоставляют пользователям доступ к механизмам,
используемым при загрузке программ, в виде системного вызова гол-ар. Этот
вызов отображает участок файла в память. Отображение возможно в двух режимах:
MAP_SHARED изменения в памяти отображаются в
файле— таким образом mmap можно использовать для реализации разделяемой
памяти и MAP_PRIVATE (соответственно, изменения
памяти в файле не отображаются — при этом измененные страницы копируются
в своп-файл).
Широко используется выделение памяти при помощи отображения псевдофайла
/dev/zero (файл бесконечной длины, состоящий из одних нулей) в память
в режиме MAP_PRIVATE.
Даже когда место под своп-файл выделяется динамически, система обычно
предоставляет возможность ограничивать его рост. У интерактивных систем
при приближении к границе емкости своп-файла система часто начинает выдавать
предупреждения пользователю. Однако основным видом реакции на переполнение
или превышение лимитов роста своп-файла является отказ выделять память
прикладным программам. Поэтому грамотно написанные программы всегда должны
проверять, нормально ли завершился запрос на выделение памяти, и по возможности
разумно обрабатывать ненормальное завершение. Это нужно не только в том
случае, когда программа будет переноситься в систему без виртуатьной памяти,
но и во вполне штатной (хотя и относительно редкой) ситуации переполнения
свопа.
Иногда, впрочем, система может выделять память (точнее, не память, а только
адресное пространство) программам без оглядки на то, сколько есть свободного
свопа. Эта довольно опасная стратегия, называемая overcommit, на первый
взгляд кажется бессмысленной или полезной только в очень специальных случаях,
например при использовании разреженных массивов. В действительности эта
стратегия оправдана и тогда, когда мы можем быть уверены, что большинство
выделенных процессу страниц никогда не будут использованы, например, при
широком применении стратегии copy-on-write.
Overcommit в Unix
В системах семейства Unix копирование при записи применяется не только
при загрузке сегментов данных программ и отображений файлов в память,
но и при создании задач. Системный вызов fork (подробнее обсуждался в
главе 3) создает полную копию адресного пространства процесса, выполнившего
этот вызов. Физического копирования, естественно, не происходит. Вместо
этого система отображает память родительского процесса в адресное пространство
потомка и устанавливает защиту от записи на все страницы обеих задач.
Когда какая-то из задач пытается осуществить запись, соответствующая страница
физически копируется и запись осуществляется уже в копию. Большинство
порожденных задач исполняют системный вызов exec вскоре после создания,
изменив лишь несколько переменных в своем адресном пространстве. При таком
стиле работы с памятью, действительно, многие выделяемые страницы не используются
никогда, а большинство из используемых только прочитываются, поэтому overcommit
является стандартной стратегией выделения памяти в Unix.