учебники, программирование, основы, введение в,

 

Объекты в памяти

Отображение объектов в адресное пространство процессов
Идея, лежащая в основе рассматриваемого класса средств, в сущности, весьма проста. Если отобразить объект в адресное пространство процесса, то доступ к объекту можно осуществлять обычными операциями чтения/записи. Если один объект отображен в адресное пространство нескольких процессов, он превращается в средство межпроцессного взаимодействия, так как данные, записанные в объект одним процессом, появляются в адресных пространствах всех участников отображения.
Отображаться могут обычные файлы, а также объекты в разделяемой и типизированной памяти.
Реализация должна обеспечивать, чтобы отображению подвергалось целое число страниц памяти из адресного пространства процесса. Размер страницы является значением конфигурационной переменной PAGESIZE. Отображаемая часть объекта должна начинаться с границы страницы. Длина отображаемой части не обязана быть кратной странице.
Попытки доступа к отображенной памяти, лежащей за текущей границей объекта, приводят к генерации сигнала SIGBUS.
К отображенным страницам могут предоставляться различные виды доступа: на чтение, запись и/или выполнение. При попытке нарушить наложенные ограничения процессу доставляется сигнал SIGSEGV.
Отображение объектов в адресное пространство процессов осуществляется функцией mmap() (см.
#include <sys/mman.h>
void *mmap (void *addr, size_t len,
int prot, int flags,
int fildes, off_t off);
Отображаемая часть объекта задается файловым дескриптором fildes, смещением off и длиной len. (Вот для чего, заметим в скобках, при открытии объекта в разделяемой памяти функцией shm_open() возвращается файловый дескриптор - чтобы передать его mmap() и отобразить объект в адресное пространство процесса, а вовсе не для того, чтобы применять к нему операции файлового ввода/вывода. В исторически сложившихся реализациях функция mmap() появилась как средство отображения в память обычных файлов, так что с расширением функциональности другие виды отображаемых объектов пришлось стричь под ту же гребенку.)
Значения аргументов addr, prot и flags задают характеристики отображения - рекомендуемый адрес в адресном пространстве процесса, запрашиваемые виды доступа к отображенным страницам и управляющие флаги.
Результатом функции mmap() служит выбранный реализацией адрес в адресном пространстве процесса, начиная с которого отображен заданный фрагмент объекта, или, в случае неудачи, значение MAP_FAILED. Если в аргументе flags установлен флаг MAP_FIXED, результирующий адрес должен совпадать со значением addr; предполагается, что приложение располагает знаниями о целевой архитектуре, достаточными для точной спецификации отображения, что, впрочем, отрицательным образом сказывается на мобильности. Если флаг MAP_FIXED не установлен, а значение addr равно NULL, реализация имеет полную свободу в выборе результирующего адреса; подобную комбинацию следует рекомендовать как максимально мобильную.
В значении аргумента flags, помимо MAP_FIXED, могут быть установлены флаги MAP_SHARED (запись в отображенную память изменяет отображаемый объект со всеми вытекающими отсюда последствиями) или MAP_PRIVATE (изменения в отображенной памяти видны только записывающему процессу и не распространяются на отображаемый объект). Стандарт POSIX-2001 не специфицирует, имеет ли место обратная зависимость, то есть видны ли процессу, установившему флаг MAP_PRIVATE, изменения в отображаемом объекте.
Аргумент prot, определяющий разрешенные виды доступа к отображенным страницам, строится как комбинация флагов PROT_READ, PROT_WRITE и PROT_EXEC (разрешен доступ, соответственно, на чтение, запись и/или выполнение). Альтернативный вариант - установить флаг PROT_NONE, запрещающий доступ к странице (из общих соображений выключатель всегда должен быть под рукой). Значение аргумента prot должно быть согласовано с флагами, указанными при открытии объекта и получении дескриптора fildes.
Стандарт POSIX-2001 предусматривает возможность динамической смены разрешенных видов доступа к отображенным страницам посредством вызова функции mprotect() (
  
#include <sys/mman.h>
int mprotect (void *addr,
size_t len, int prot);
Виды доступа меняются в соответствии со значением аргумента prot для всех страниц, пересекающихся с частью адресного пространства процесса, начинающейся со значения addr (которое должно указывать на границу страницы) и имеющей длину len байт.
Нормальным результатом функции mprotect() (и описываемых далее функций) служит нуль; в случае ошибки возвращается -1.
Для отмены отображений в адресное пространство процессов служит функция munmap() (см.
  
#include <sys/mman.h>
int munmap (void *addr, size_t len);
Отображения отменяются для страниц, пересекающихся с частью адресного пространства процесса, начинающейся со значения addr (которое должно указывать на границу страницы) и имеющей длину len байт.
Отображение объектов в адресное пространство процессов - универсальное, исключительно мощное средств. В качестве примера применим его для реализации упрощенного аналога служебной программы cat (см.
Отметим, что обрабатываемые файлы только открываются, но не читаются, а отображаются в память, откуда и выдаются на стандартный вывод. Роль буфера играют отображенные страницы.


При работе с объектами в памяти полезны функции truncate() и, особенно, ftruncate() (см.), позволяющие установить размер объекта равным заданной величине length. Напомним, что попытка записи за пределы отображенного объекта не только не расширит его, но, весьма вероятно, приведет к доставке процессу сигнала SIGBUS.
#include <unistd.h>

int truncate (const char *path,
off_t length);

int ftruncate (int fildes, off_t length);
Функции truncate() и ftruncate() применимы к обычным файлам, которые в первом случае задаются маршрутным именем, а во втором - открытым дескриптором. Кроме того, функция ftruncate() применима к объектам в разделяемой памяти (и это единственная узаконенная стандартом POSIX-2001 файловая операция над дескриптором подобного объекта). Других способов изменить размер объекта в разделяемой памяти стандарт не предусматривает.
Применение объектов в разделяемой памяти в сочетании с функциями mmap() и ftruncate() иллюстрируется модифицированным вариантом двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод (см. Как и для исходного варианта, предполагается, что заголовочный файл называется "g_shm.h", а файл с образом процесса, запускаемого посредством execl(), - "g_r_shm".
  
#ifndef g_SHM
#define g_SHM

/* Имя объекта в разделяемой памяти */
#define O_SHM_NAME             "/g_o.shm"

/* Номер сигнала для синхронизации
/* доступа к разделяемому сегменту */
#define SIG_SHM        SIGALRM

#endif 
В модифицированном варианте сигналы (не имеет значения - обычные или реального времени) применяются только как средство синхронизации. Для выявления конца файла задействован механизм контроля длительности ожидания. Читателю предлагается поварьировать эти длительности, немного задержаться с вводом строк с терминала и проанализировать поведение программы.
Отметим, что при использовании объектов в разделяемой памяти, отображенных в адресные пространства процессов, передачи данных как таковой не требуется. В приведенной программе начальный процесс читает строки в разделяемый объект, а порожденный берет их оттуда же и выводит без каких-либо дополнительных копирований. Флаг MAP_SHARED в вызове mmap() обеспечивает доступность данных, записанных в объект одним процессом, всем другим процессам-читателям.
Еще одна возможность, полезная в связи с отображением объектов в адресное пространство процессов, - синхронизация (согласование состояния) оперативной и долговременной памяти. Это возможность реализует функция msync()
#include <sys/mman.h>
int msync (void *addr,
size_t len, int flags);
Функция msync() записывает в долговременную память все измененные страницы объекта в памяти, пересекающиеся с фрагментом адресного пространства процесса, начинающимся с адреса addr (это должна быть граница страницы) и имеющим длину len байт.
Если объектом является файл, отображенный в память (естественно, без флага MAP_PRIVATE), то вызов msync() гарантирует завершение всех операций записи с обеспечением целостности данных синхронизированного ввода/вывода (см. курс [1]). Эффект от применения msync() к объектам в разделяемой и типизированной памяти не специфицирован.
Аргумент flags определяет характер действий при записи измененных страниц. Флаг MS_ASYNC означает асинхронную, а MS_SYNC - синхронную запись. Кроме того, флаг MS_INVALIDATE предписывает выполнить инициализацию кэша данных (приведение его в соответствие с содержимым долговременной памяти).
Отметим, что функция msync() обязана присутствовать только на системах, поддерживающих две необязательные возможности стандарта POSIX-2001 - файлы, отображенные в память, и синхронизированный ввод/вывод. В этом смысле ее можно считать дважды необязательной.
Вообще говоря, вызов msync() - не единственная причина, способная инициировать запись в долговременную память; подобная запись может стать следствием нормальной системной активности.
Проиллюстрируем работу с файлами, отображенными в память, еще одним примером, реализующим некоторые функции систем управления базами данных в памяти
Приведенная программа следует фундаментальному принципу неуничтожения информации. При модификациях порождаются новые версии объектов; первоначальные объекты остаются неизменными. Подобный подход особенно полезен, когда СУБД является технологическим элементом инструментальной среды разработки программ, поскольку историю создания и модификации программных систем обязательно нужно сохранять.
Обратим внимание на изменение разрешенных видов доступа к отображенным страницам, реализуемое обращениями к функции mprotect(). Первоначально отображенная память доступна только на чтение. При попытке записи в отображенную память генерируется сигнал SIGSEGV, и функция его обработки добавляет доступ на запись к соответствующей странице. В результате появляется возможность отслеживания изменений и сохранения в последующем только измененных страниц, так что дисковое пространство на версии объектов будет расходоваться экономно.
Выбранный способ выявления измененных страниц путем пробных записей может показаться несколько вычурным (в функции обработки сигнала SIGSEGV можно было бы формировать список адресов страниц, в которые производится запись, и тогда в функции фиксации изменений достаточно просто пройти по этому списку), но он вытекает из еще одного важного принципа - в максимальной степени упрощать и ускорять обычную работу, даже за счет возможного усложнения и замедления редко исполняемых фрагментов.
К сожалению, приведенная программа не вполне соответствует стандарту POSIX-2001 в части обработки сигналов. По стандарту поведение процесса после нормального выхода из функций обработки сигналов SIGBUS, SIGFPE, SIGILL и SIGSEGV не определено, если только последние не были сгенерированы вызовами kill(), sigqueue() или raise(). (Заметим попутно, что и игнорирование перечисленных сигналов приводит к неопределенному поведению.) Строго говоря, и вызов mprotect() из функций обработки сигналов не считается безопасным. В общем, функция sigsegv_sigaction() - сама нестандартность и немобильность, и это, конечно, плохо, но то, что вся нестандартность и немобильность программы сосредоточена в одной небольшой функции, безусловно, хорошо.

Объекты в типизированной памяти
Объекты в типизированной памяти - это конфигурируемые реализацией именованные пулы памяти, доступные одному или нескольким процессорам системы через один или несколько портов. Пример подобной конфигурации показан на.
Здесь пул памяти M4 доступен только процессору P2. Пул M2 включает в себя пулы M2.1 и M2.2, причем доступ к M2.1 возможен только через порт B1. Остальные пулы доступны обоим процессорам через указанные на рисунке порты.
Разные пулы памяти могут обладать различными операционными характеристиками. Это может быть статическая или динамическая память, ПЗУ, энергонезависимая память и т.п.
Каждая допустимая комбинация пула памяти и порта идентифицируется именем, определяемым при конфигурировании системы способом, зависящим от реализации. Используя это имя, объект в типизированной памяти можно открыть и отобразить в адресное пространство процесса. Реализация должна поддерживать как динамическое выделение (резервирование) памяти из пула (аналог malloc(), когда приложение задает только запрашиваемый объем), так и отображение части пула с заданным в приложении смещением от начала. Зарезервированный фрагмент в последующем подлежит освобождению. Наконец, при доступе через определенный порт нужно иметь возможность выяснить смещение и размер непрерывного участка типизированной памяти.
Согласно стандарту POSIX-2001, для открытия объектов в типизированной памяти служит функция posix_typed_mem_open()).
#include <sys/mman.h>
int posix_typed_mem_open (const char *name,
int oflag, int tflag);
Листинг 5.11. Описание функции posix_typed_mem_open().
Нормальным результатом функции posix_typed_mem_open() является файловый дескриптор объекта в типизированной памяти с именем name.
Аргумент oflag задает один из трех разрешенных видов доступа к объекту - O_RDONLY, O_WRONLY или O_RDWR.
Значение аргумента tflag определяет поведение объекта в типизированной памяти при последующих отображениях посредством функции mmap(). Может быть установлен один из трех флагов:
POSIX_TYPED_MEM_ALLOCATE
При вызове mmap() резервировать и отображать память, возможно, состоящую из нескольких несмежных сегментов (но в любом случае отображается она в непрерывный фрагмент адресного пространства процесса).
POSIX_TYPED_MEM_ALLOCATE_CONTIG
При вызове mmap() резервировать и отображать один непрерывный сегмент памяти.
POSIX_TYPED_MEM_MAP_ALLOCATABLE
При вызове mmap() отображать объект в адресное пространство процесса, не воздействуя на возможности резервирования памяти.
Если не установлен ни один из описанных флагов, указанный фрагмент объекта отображается в адресное пространство процесса и становится недоступным для резервирования; после отмены отображения возможность резервирования восстанавливается.
Отметим, что, в отличие от объектов в разделяемой памяти, объекты в типизированной памяти нельзя создать - их можно только открыть. Не применима к ним (точнее, к их открытым дескрипторам) и функция установки размера ftruncate().
После того, как объект в типизированной памяти открыт, посредством функции posix_typed_mem_get_info() (см) можно выяснить максимальный объем памяти, доступной для резервирования. Это важно, поскольку типизированная память зачастую является дефицитным ресурсом.
#include <sys/mman.h>
int posix_typed_mem_get_info (int fildes,
struct posix_typed_mem_info *info);
Листинг 5.12. Описание функции posix_typed_mem_get_info().
Соответствующая информация возвращается в структуре типа posix_typed_mem_info, на которую указывает аргумент info. Согласно стандарту POSIX-2001, эта структура должна содержать поле
size_t posix_tmi_length;
/* Максимальный объем памяти,  */
/* доступной для резервирования*/
Функция posix_typed_mem_get_info() принимает во внимание флаг POSIX_TYPED_MEM_ALLOCATE или POSIX_TYPED_MEM_ALLOCATE_CONTIG, если он был установлен при открытии объекта в типизированной памяти. При отсутствии этих флагов результат вызова posix_typed_mem_get_info() не специфицирован.
Разумеется, возвращаемый максимальный объем памяти, доступной для резервирования, есть величина, динамически изменяющаяся. Если объект в типизированной памяти является разделяемым, то нет гарантии, что в интервале между опросом и попыткой резервирования этот объем останется прежним.
Еще одним проявлением общего принципа "если объект может измениться, должна быть возможность опросить его текущее состояние" является наличие в стандарте POSIX-2001 функции posix_mem_offset() (см.
int posix_mem_offset (
const void *restrict addr,
size_t len, off_t *restrict off,
size_t *restrict contig_len,
int *restrict fildes);
Листинг 5.13. Описание функции posix_mem_offset(). (Функция posix_mem_offset() позволяет выяснить адрес (смещение от начала), длину и дескриптор объекта (блока) в типизированной памяти, отображенного в адресное пространство процесса, начиная с адреса addr и имеющего длину len. Искомое смещение записывается по указателю off. По указателю contig_len помещается минимум из значения len и длины максимального непрерывного блока типизированной памяти, отображенного, начиная с addr. По указателю fildes выдается дескриптор, использованный в вызове mmap() при задании опрашиваемого отображения (если с тех пор дескриптор закрыли, выдается -1).
Если дескриптор объекта в типизированной памяти был открыт без флагов POSIX_TYPED_MEM_ALLOCATE и POSIX_TYPED_MEM_ALLOCATE_CONTIG, то возвращенные функцией posix_mem_offset() значения, будучи подставлены в вызов mmap(), зададут отображение в точности того же блока типизированной памяти, что и при первоначальном обращении к mmap().
Поведение функции posix_mem_offset() при попытке опросить характеристики отображения объекта, не являющегося объектом в типизированной памяти, зависит от реализации.
Рассмотрим возможную последовательность применения описанных функций. Пусть процессу A1, выполняющемуся на процессоре P1 (см. выше), требуется зарезервировать блок памяти из пула M2, причем этот блок предполагается использовать совместно с процессом A2, выполняющемся на процессоре P2. Поскольку P2 имеет доступ только к M2.2, процессы должны использовать именно эту часть M2.
В качестве первого шага процесс A1 вызывает функцию posix_typed_mem_open() с именем объекта "/m2.2-b1" (или каким-то еще в том же духе, включающим имена пула и порта) и установленным в аргументе tflag флагом POSIX_TYPED_MEM_ALLOCATE, получая открытый дескриптор, пригодный для резервирования памяти. Затем A1 обращается с этим дескриптором к функции mmap(), указывая в качестве длины довольно большое значение, например, 1048576. Пусть в ответ на запрос система выделяет два несмежных блока типизированной памяти длиной 524288 байт каждый, отображает их в один непрерывный фрагмент адресного пространства процесса A1 и возвращает указатель на него. Теперь процесс A1 может стандартным образом работать с полученным мегабайтным массивом.
Если процесс A1 пожелает выяснить, какие части пула M2.2 были зарезервированы, он должен обратиться к функции posix_mem_offset() с адресом первого элемента массива и полной длиной (1048576). В ответ он получит смещение и длину первого из выделенных блоков (524288). Поскольку возвращенная длина меньше запрошенной, требуется повторный вызов posix_mem_offset() со смещением в полмегабайта от начала массива и соответственно уменьшенной длиной. (В общем случае, разумеется, следует организовать цикл, вызывая posix_mem_offset() до тех пор, пока не будет исчерпан весь отображенный массив. Чтобы избежать подобных относительно сложных действий, можно при открытии объекта в типизированной памяти установить в tflag флаг POSIX_TYPED_MEM_ALLOCATE_CONTIG, но тогда, возможно, попытка резервирования непрерывного мегабайтного блока закончится неудачей.)
Чтобы обеспечить совместное использование типизированной памяти с процессом A2, A1 должен каким-то образом (очевидно, с помощью средств межпроцессного взаимодействия) передать тому смещения и длины зарезервированных блоков. Получив эти данные, A2 обращается к posix_typed_mem_open() с именем "/m2.2-b2" (доступ через порт B2) и нулевым значением tflag, а затем дважды вызывает mmap() для отображения обоих непрерывных фрагментов типизированной памяти в свое адресное пространство. Вероятно, при втором вызове целесообразно указать соответствующий адрес начала и флаг MAP_FIXED, чтобы "склеить" эти отображения и, как и в случае с A1, получить непрерывный мегабайтный массив.

Средства удержания процессов в памяти
Современные системы имеют сложную аппаратную организацию. Отметим следующие особенности таких систем, существенные для приложений реального времени.

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

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

int mlockall (int flags);

int munlockall (void);
Листинг 5.16. Описание функций mlockall() и munlockall().Поскольку адресное пространство процесса при выполнении, вообще говоря, меняется, необходимо уточнить, что именно должно удерживаться в памяти. Для этого служит аргумент flags функции mlockall(), в значении которого могут фигурировать следующие флаги.
MCL_CURRENT
Удерживать в памяти страницы, присутствующие в адресном пространстве процесса на момент вызова.
MCL_FUTURE
Удерживать в памяти страницы, которые будут отображены в адресное пространство процесса после вызова.
Очевидно, установка обоих флагов позволяет удерживать в памяти и текущие, и будущие страницы.
Если установлен флаг MCL_FUTURE, то со временем общий объем удерживаемых страниц может превысить размеры физической памяти. В таком случае поведение операционной системы зависит от реализации.
Функция munlockall() отменяет удержание для всех страниц, присутствующих в адресном пространстве процесса на момент вызова или отображенных позднее, если только при вызове mlockall() не был установлен флаг MCL_FUTURE.
Определенную проблему составляет удержание в памяти страниц стека, рост которого не всегда можно точно оценить. (А стек, конечно, нуждается в удержании, иначе, например, функция обработки сигнала может выполниться с неожиданной задержкой.) Одно из возможных решений этой проблемы состоит в установке флага MCL_FUTURE при обращении к mlockall() и последующем вызове вспомогательной функции, в которой продекларирован автоматический массив достаточного размера (см. Листинг 5.17. Пример программы, удерживающей в памяти растущий стек. При написании такого рода программ следует позаботиться о защите от слишком интеллектуального оптимизирующего компилятора, который не только способен избавиться от неиспользуемых локальных переменных, но и может не включать в выходной код ненужные функции.

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г.Яндекс.Метрика