Средства удержания процессов в памяти
Современные системы имеют сложную аппаратную организацию. Отметим следующие особенности таких систем, существенные для приложений реального времени.
- поддержка страничной виртуальной памяти с подкачкой по запросу;
- применение нескольких уровней кэширования;
- наличие нескольких видов физической памяти с разными характеристиками (быстрой статической, более медленной, но и более дешевой динамической и т.п.).
Подкачка страниц, непопадание в кэш, обращение к более медленной памяти вносят элементы недетерминированности в работу приложений. Чтобы по возможности избавиться от них, в стандарт POSIX введены средства удержания в физической памяти страниц из адресного пространства процессов. Они не ликвидируют недетерминированность полностью (например, ничего не говорится о длительности трансляции логического адреса в физический), но позволяют обеспечить разумную верхнюю границу времени доступа к командам и данным процессов.
Если реализация не поддерживает виртуальной памяти, то описываемые далее функции могут просто ничего не делать (кроме, быть может, проверки корректности аргументов).
Для удержания в физической памяти группы страниц из адресного пространства процесса служит функция mlock() (см. листинг 5.14), воспользоваться которой может только процесс, обладающий соответствующими привилегиями (см. курс [1], где раскрывается понятие "соответствующие привилегии").
#include <sys/mman.h> int mlock (const void *addr, size_t len);
Листинг 5.14. Описание функции mlock(). (html, txt)
После успешного завершения вызова mlock() резидентными в памяти станут все страницы, пересекающиеся с частью адресного пространства процесса, начинающейся со значения addr и имеющей длину len байт. Реализация может требовать, чтобы значение addr указывало на границу страницы.
Если некоторая страница несколько раз отображена в адресные пространства процессов, то для ее удержания в памяти достаточно позаботиться о каком-либо одном отображении.
Удержание отменяется после вызовов fork() и exec(), а также ликвидации по какой-либо причине соответствующей части адресного пространства процесса (отмена отображения, завершение процесса и т.п.).
Явная отмена удержания группы страниц реализуется функцией munlock() (см. листинг 5.15).
#include <sys/mman.h> int munlock (const void *addr, size_t len);
Листинг 5.15. Описание функции munlock(). (html, txt)
Более точно, munlock() разрешает больше не удерживать в памяти заданные страницы из адресного пространства вызывающего процесса. Если страница была отображена несколько раз и удерживалась в памяти несколькими вызовами mlock(), то одного обращения к munlock() для отмены удержания недостаточно.
Если нужно удерживать в памяти все адресное пространство процесса (что имеет место для большинства приложений реального времени), целесообразно воспользоваться функциями mlockall() и munlockall() (см. листинг 5.16).
#include <sys/mman.h>
int mlockall (int flags);
int munlockall (void);
Листинг 5.16. Описание функций mlockall() и munlockall(). (html, txt)
Поскольку адресное пространство процесса при выполнении, вообще говоря, меняется, необходимо уточнить, что именно должно удерживаться в памяти. Для этого служит аргумент flags функции mlockall(), в значении которого могут фигурировать следующие флаги.
MCL_CURRENT
Удерживать в памяти страницы, присутствующие в адресном пространстве процесса на момент вызова.
MCL_FUTURE
Удерживать в памяти страницы, которые будут отображены в адресное пространство процесса после вызова.
Очевидно, установка обоих флагов позволяет удерживать в памяти и текущие, и будущие страницы.
Если установлен флаг MCL_FUTURE, то со временем общий объем удерживаемых страниц может превысить размеры физической памяти. В таком случае поведение операционной системы зависит от реализации.
Функция munlockall() отменяет удержание для всех страниц, присутствующих в адресном пространстве процесса на момент вызова или отображенных позднее, если только при вызове mlockall() не был установлен флаг MCL_FUTURE.
Определенную проблему составляет удержание в памяти страниц стека, рост которого не всегда можно точно оценить. (А стек, конечно, нуждается в удержании, иначе, например, функция обработки сигнала может выполниться с неожиданной задержкой.) Одно из возможных решений этой проблемы состоит в установке флага MCL_FUTURE при обращении к mlockall() и последующем вызове вспомогательной функции, в которой продекларирован автоматический массив достаточного размера (см.листинг 5.17).
Листинг 5.17. Пример программы, удерживающей в памяти растущий стек. (html, txt)
При написании такого рода программ следует позаботиться о защите от слишком интеллектуального оптимизирующего компилятора, который не только способен избавиться от неиспользуемых локальных переменных, но и может не включать в выходной код ненужные функции.