Идея, лежащая в основе рассматриваемого класса средств, в сущности, весьма проста. Если отобразить объект в
Отображаться могут
Реализация должна обеспечивать, чтобы отображению подвергалось целое число PAGESIZE. Отображаемая часть объекта должна начинаться с границы страницы. Длина отображаемой части не обязана быть кратной странице.
Попытки доступа к отображенной памяти, лежащей за текущей границей объекта, приводят к SIGBUS.
К отображенным страницам могут предоставляться различные SIGSEGV.
mmap() (см. листинг 5.1).
#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() (см. листинг 5.2).
#include <sys/mman.h>
int mprotect (void *addr,
size_t len, int prot);
Виды доступа меняются в соответствии со значением аргумента prot для всех страниц, пересекающихся с частью addr (которое должно указывать на границу страницы) и имеющей длину len байт.
Нормальным результатом функции mprotect() (и описываемых далее функций) служит нуль; в случае ошибки возвращается -1.
Для munmap() (см. листинг 5.3).
#include <sys/mman.h> int munmap (void *addr, size_t len);
Отображения отменяются для страниц, пересекающихся с частью addr (которое должно указывать на границу страницы) и имеющей длину len байт.
cat (см. листинг 5.4).
/* * * * * * * * * * * * * * * * * * * * * * * */
/* Программа выдает на стандартный вывод файлы,*/
/* имена которых заданы в командной строке */
/* * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
int main (int argc, char *argv []) {
struct stat sbuf; /* Структура для получения информации */
/* о файле */
int fd; /* Дескриптор обрабатываемого файла */
void *maddr; /* Адрес отображенного в память файла */
int i;
for (i = 1; i < argc; i++) {
if ((fd = open (argv [i], O_RDONLY)) < 0) {
perror ("OPEN");
continue;
}
if (fstat (fd, sbuf) != 0) {
perror ("FSTAT");
close (fd);
continue;
}
if ((maddr = mmap (NULL, sbuf.st_size,
PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED){
perror ("MMAP");
close (fd);
continue;
}
if (write (STDOUT_FILENO, maddr,
sbuf.st_size) != sbuf.st_size) {
perror ("WRITE");
}
if (munmap (maddr, sbuf.st_size) != 0) {
perror ("MUNMAP");
}
close (fd);
} /* for */
return 0;
}
Отметим, что обрабатываемые файлы только открываются, но не читаются, а отображаются в память, откуда и выдаются на стандартный вывод. Роль буфера играют отображенные страницы.
При работе с объектами в памяти полезны функции truncate() и, особенно, ftruncate() (см. листинг 5.5), позволяющие установить размер объекта равным заданной величине length. Напомним, что попытка записи за пределы отображенного объекта не только не расширит его, но, весьма вероятно, приведет к доставке процессу сигнала SIGBUS.
#include <unistd.h>
int truncate (const char *path,
off_t length);
int ftruncate (int fildes, off_t length);
Функции truncate() и ftruncate() применимы к ftruncate() применима к
Применение mmap() и ftruncate() иллюстрируется модифицированным вариантом двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод (см. листинги 5.6, 5.7, 5.8). Как и для исходного варианта, предполагается, что заголовочный файл называется "g_shm.h", а файл с образом процесса, запускаемого посредством .
#ifndef g_SHM #define g_SHM /* Имя объекта в разделяемой памяти */ #define O_SHM_NAME "/g_o.shm" /* Номер сигнала для синхронизации /* доступа к разделяемому сегменту */ #define SIG_SHM SIGALRM #endif
/* * * * * * * * * * * * * * * * * * * * * */
/* Программа, состоящая из двух процессов, */
/* копирует строки со стандартного ввода на*/
/* стандартный вывод, используя в качестве */
/* буфера разделяемый сегмент, отображенный*/
/* в адресные пространства процессов. */
/* Для синхронизации доступа к разделяемому*/
/* сегменту используется сигнал SIGALRM */
/* * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/wait.h>
#include <assert.h>
#include "g_shm.h"
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Создание разделяемого сегмента памяти, */
/* отображение его в адресное пространство процесса, */
/* чтение строк со стандартного ввода в сегмент */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
int main (void) {
int fd_shm; /* Дескриптор объекта в разделяемой памяти */
void *addr_shm; /* Адрес отображенного в память объекта */
sigset_t smask; /* Маска блокируемых сигналов */
siginfo_t sinfo;/* Структура для получения данных о сигнале */
/* Длительность ожидания прихода сигнала */
struct timespec stmspc = {10, 0};
pid_t cpid; /* Идентификатор порожденного процесса */
/* Создадим разделяемый сегмент памяти */
if ((fd_shm = shm_open (O_SHM_NAME, O_RDWR | O_CREAT,
0777)) < 0) {
perror ("SHM_CREAT");
return (1);
}
/* Сделаем размер созданного объекта равным LINE_MAX */
if (ftruncate (fd_shm, LINE_MAX) != 0) {
perror ("FTRUNCATE");
return (2);
}
/* Отобразим сегмент в память */
if ((addr_shm = mmap (NULL, LINE_MAX,
PROT_READ | PROT_WRITE, MAP_SHARED,
fd_shm, 0)) == MAP_FAILED) {
perror ("MMAP-P");
return (3);
}
/* Сформируем маску сигналов (блокируем SIG_SHM) */
(void) sigemptyset (smask);
(void) sigaddset (smask, SIG_SHM);
(void) sigprocmask (SIG_BLOCK, smask,
(sigset_t *) NULL);
/* Подготовительная работа закончена */
switch (cpid = fork ()) {
case -1:
perror ("FORK");
return (4);
case 0:
/* Чтение из объекта и выдачу на стандартный*/
/* вывод реализуем в порожденном процессе*/
if (execl ("./g_r_shm", "g_r_shm", (
char *) NULL) < 0) {
perror ("EXECL");
return (5);
}
}
/* Чтение строк со стандартного ввода в объект */
/* возложим на родительский процесс. */
/* В начальный момент объект в разделяемой памяти */
/* доступен для записи*/
while (fgets (addr_shm, LINE_MAX, stdin) != NULL) {
/* Сообщим порожденному процессу, */
/* что в объект в разделяемой памяти */
/* помещена очередная строка */
assert (kill (cpid, SIG_SHM) == 0);
/* Дождемся, когда в объект можно будет прочитать*/
/* следующую строку */
if (sigtimedwait (smask,
sinfo, stmspc) != SIG_SHM) {
break;
}
}
/* Порожденный процесс должен завершиться*/
/* по контролю времени ожидания*/
(void) wait (NULL);
if (shm_unlink (O_SHM_NAME) != 0) {
perror ("SHM_UNLINK");
return (6);
}
return (0);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Процесс берет строки из объекта в разделяемой памяти*/
/* и выдает их на стандартный вывод */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <limits.h>
#include <assert.h>
#include "g_shm.h"
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Открытие разделяемого сегмента памяти, */
/* отображение его в адресное пространство процесса, */
/* чтение из сегмента и выдача строк на стандартный вывод */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
int main (void) {
int fd_shm; /* Дескриптор объекта в разделяемой */
/* памяти */
void *addr_shm; /* Адрес отображенного в память */
/* объекта */
sigset_t smask; /* Маска ожидаемых сигналов */
siginfo_t sinfo; /* Структура для получения данных*/
/* о сигнале */
/* Длительность ожидания строки */
struct timespec stmspc = {10, 0};
pid_t ppid; /* Идентификатор родительского процесса */
/* Откроем разделяемый сегмент памяти */
if ((fd_shm = shm_open (O_SHM_NAME, O_RDONLY,
0777)) < 0) {
perror ("SHM_OPEN");
return (1);
}
/* Отобразим сегмент в память */
if ((addr_shm = mmap (NULL, LINE_MAX, PROT_READ,
MAP_SHARED, fd_shm, 0)) == MAP_FAILED) {
perror ("MMAP-C");
return (2);
}
/* Запомним идентификатор родительского процесса */
ppid = getppid ();
/* Сформируем маску ожидаемых сигналов (SIG_SHM) */
(void) sigemptyset (smask);
(void) sigaddset (smask, SIG_SHM);
/* Подготовительная работа закончена */
fputs ("Вводите строки\n", stdout);
while (sigtimedwait (smask, sinfo, stmspc) == SIG_SHM) {
/* Дождались, когда в объекте появилась строка.*/
/* Выдадим ее на стандартный вывод*/
assert (fputs ("Вы ввели: ", stdout) != EOF);
assert (fputs (addr_shm, stdout) != EOF);
/* Сообщим родительскому процессу, */
/* что данные из объекта извлечены */
assert (kill (ppid, SIG_SHM) == 0);
}
return 0;
}
В модифицированном варианте сигналы (не имеет значения - обычные или реального времени) применяются только как средство синхронизации. Для выявления конца файла задействован механизм контроля
Отметим, что при использовании MAP_SHARED в вызове mmap() обеспечивает доступность данных, записанных в объект одним процессом, всем другим процессам-читателям.
Еще одна возможность, полезная в связи с msync() (см. листинг 5.9).
#include <sys/mman.h>
int msync (void *addr,
size_t len, int flags);
Функция msync() записывает в долговременную память все измененные страницы объекта в памяти, пересекающиеся с фрагментом адресного пространства процесса, начинающимся с адреса addr (это должна быть граница страницы) и имеющим длину len байт.
Если объектом является файл, отображенный в память (естественно, без флага MAP_PRIVATE ), то вызов msync() гарантирует завершение всех операций записи с обеспечением msync() к
Аргумент flags определяет характер действий при записи измененных страниц. Флаг MS_ASYNC означает MS_SYNC - MS_INVALIDATE предписывает выполнить
Отметим, что функция msync() обязана присутствовать только на системах, поддерживающих две необязательные возможности стандарта POSIX-2001 - файлы, отображенные в память, и
Вообще говоря, вызов msync() - не единственная причина, способная инициировать запись в долговременную память; подобная запись может стать следствием нормальной системной активности.
Проиллюстрируем работу с файлами, отображенными в память, еще одним примером, реализующим некоторые функции систем управления базами данных в памяти (см. листинг 5.10).
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа реализует некоторые функции систем управления базами*/
/* данных в памяти, следуя принципу неуничтожения информации */
/* (изменения записываются не в сам объет, а в его новую версию) */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
#include s<ys/mman.h>
#include <assert.h>
/* Размер страницы */
static int pgsz;
/* Структура для получения */
/* информации об исходном файле*/
static struct stat sbuf_src;
/* Адрес отображенного в память исходного файла*/
static char *saddr;
/* Признак того, что выполнялась функция */
/* обработки сигнала SIGSEGV */
static char sigsegv_catching;
/* * * * * * * * * * * * * * * * * * * * */
/* Функция обработки сигнала SIGSEGV. */
/* Предполагается, что этот сигнал */
/* генерируется при попытке записи в */
/* страницы, доступные только на чтение. */
/* Функция добавляет разрешение на запись*/
/* * * * * * * * * * * * * * * * * * * * */
static void sigsegv_sigaction (int sig, siginfo_t *sig_info,
void *addr) {
void *page_addr; /* Адрес страницы, в которую*/
/*пытались писать*/
page_addr = (char *) sig_info->si_addr - (off_t)
((char *) sig_info->si_addr - saddr) % pgsz;
assert (mprotect (page_addr, pgsz,
PROT_READ | PROT_WRITE) == 0);
sigsegv_catching = 1;
}
/* * * * * * * * * * * * * * * * * * * * * * * */
/* Установка способа обработки сигнала SIGSEGV */
/* * * * * * * * * * * * * * * * * * * * * * * */
static int sigsegv_set_proc (void (*sigsegv_actn) (int,
siginfo_t *, void *), struct sigaction *old_sigsegv_sact) {
struct sigaction sact;
(void) sigemptyset (sact.sa_mask);
sact.sa_flags = SA_SIGINFO;
sact.sa_sigaction = sigsegv_actn;
if (sigaction (SIGSEGV, sact, old_sigsegv_sact) != 0) {
perror ("SIGACTION");
return (-1);
}
return 0;
}
/* * * * * * * * * * * * * * * * * * * * * */
/* Функция начала работы с исходным файлом.*/
/* Нормальный результат равен нулю */
/* * * * * * * * * * * * * * * * * * * * * */
static int init_src (char *name_src) {
int fd_src; /* Дескриптор исходного файла */
/* Откроем исходный файл и отобразим его в память */
if ((fd_src = open (name_src, O_RDONLY)) < 0) {
perror ("OPEN-SRC");
return (-1);
}
if (fstat (fd_src, sbuf_src) != 0) {
perror ("FSTAT");
return (-1);
}
if ((saddr = (char *) mmap (NULL, sbuf_src.st_size,
PROT_READ, MAP_PRIVATE, fd_src, 0)) == MAP_FAILED) {
perror ("MMAP");
return (-1);
}
return 0;
}
/* * * * * * * * * * * * * * * * * * */
/* Опрос конфигурационных параметров */
/* * * * * * * * * * * * * * * * * * */
static int init_conf_params (void) {
if ((pgsz = sysconf (_SC_PAGESIZE)) == -1) {
perror ("SYSCONF");
return (-1);
}
return 0;
}
/* * * * * * * * * * * */
/* Общая инициализация */
/* * * * * * * * * * * */
static int init_all (char *name_src) {
if ((init_src (name_src) != 0) || (sigsegv_set_proc
(sigsegv_sigaction, (struct sigaction *) NULL) != 0) ||
(init_conf_params () != 0)) {
return (-1);
}
return 0;
}
/* * * * * * * * * * * * * * * * * */
/* Запись в файл измененных страниц*/
/* * * * * * * * * * * * * * * * * */
static int write_dlt (char *name_dlt) {
int fd_dlt; /* Дескриптор файла изменений*/
volatile char tmp; /* Значение пробного байта*/
off_t pos;/* Позиция в отображенной памяти*/
if ((fd_dlt = open (name_dlt,
O_CREAT | O_WRONLY | O_TRUNC, 0777)) < 0) {
perror ("OPEN-DLT");
return (-1);
}
/* Измененные страницы выявим путем пробных записей. */
for (pos = 0; pos < sbuf_src.st_size; pos += pgsz) {
tmp = saddr [pos];
sigsegv_catching = 0;
saddr [pos] = tmp;
if (sigsegv_catching == 0) {
/* В страницу удалось записать */
/* без генерации сигнала SIGSEGV.*/
/* Значит, она была доступна на запись.*/
/* Следовательно, ее модифицировали.*/
/* Запишем ее в файл изменений */
(void) lseek (fd_dlt, pos, SEEK_SET);
assert (write (fd_dlt, saddr + pos, pgsz) == pgsz);
}
/* Уберем добавленное разрешение на запись */
assert (mprotect (saddr + pos, pgsz,
PROT_READ) == 0);
} /* for */
return (close (fd_dlt));
}
/* * * * * * * * * * * * * */
/* Функция main() вызывает */
/* описанные выше функции */
/* и обращается на запись */
/* к отображенной памяти */
/* * * * * * * * * * * * * */
int main (int argc, char *argv []) {
if (argc != 3) {
fprintf (stderr, "Использование: %s исходный_файл "
"файл_изменений\n", argv [0]);
return (-1);
}
if (init_all (argv [1]) != 0) {
fprintf (stderr, "Ошибка инициализации\n");
return (-1);
}
printf ("Размер страницы: %d\n"
"Адрес отображенного в память исходного файла: %p\n"
"Длина отображенного в память исходного файла: %ld\n",
pgsz, saddr, sbuf_src.st_size);
saddr [0] = '*';
return (write_dlt (argv [2]));
}
Приведенная программа следует фундаментальному принципу неуничтожения информации. При модификациях порождаются новые версии объектов; первоначальные объекты остаются неизменными. Подобный подход особенно полезен, когда СУБД является технологическим элементом инструментальной среды разработки программ, поскольку историю создания и модификации программных систем обязательно нужно сохранять.
Обратим внимание на изменение разрешенных mprotect(). Первоначально отображенная память доступна только на чтение. При попытке записи в отображенную память генерируется сигнал SIGSEGV, и функция его обработки добавляет доступ на запись к соответствующей странице. В результате появляется возможность отслеживания изменений и сохранения в последующем только измененных страниц, так что дисковое пространство на версии объектов будет расходоваться экономно.
Выбранный способ выявления измененных страниц путем пробных записей может показаться несколько вычурным (в SIGSEGV можно было бы формировать список адресов страниц, в которые производится запись, и тогда в функции фиксации изменений достаточно просто пройти по этому списку), но он вытекает из еще одного важного принципа - в максимальной степени упрощать и ускорять обычную работу, даже за счет возможного усложнения и замедления редко исполняемых фрагментов.
К сожалению, приведенная программа не вполне соответствует стандарту POSIX-2001 в части обработки сигналов. По стандарту поведение процесса после нормального выхода из функций обработки сигналов SIGBUS, SIGFPE, SIGILL и SIGSEGV не определено, если только последние не были сгенерированы вызовами kill(), sigqueue() или raise(). (Заметим попутно, что и игнорирование перечисленных сигналов приводит к неопределенному поведению.) Строго говоря, и вызов mprotect() из функций обработки сигналов не считается безопасным. В общем, функция sigsegv_sigaction() - сама нестандартность и немобильность, и это, конечно, плохо, но то, что вся нестандартность и немобильность программы сосредоточена в одной небольшой функции, безусловно, хорошо.
(рис 5.1) Пример многопроцессорной системы с несколькими пулами памяти
Здесь M4 доступен только процессору P2. Пул M2 включает в себя пулы M2.1 и M2.2, причем доступ к M2.1 возможен только через B1. Остальные пулы доступны обоим процессорам через указанные на рисунке
Разные
Каждая допустимая комбинация malloc(), когда приложение задает только запрашиваемый объем), так и
posix_typed_mem_open() (см. листинг 5.11)
#include <sys/mman.h> int posix_typed_mem_open (const char *name, int oflag, int tflag);
Нормальным результатом функции 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() (см. листинг 5.12) можно выяснить максимальный объем памяти, доступной для резервирования. Это важно, поскольку
#include <sys/mman.h> int posix_typed_mem_get_info (int fildes, struct posix_typed_mem_info *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() (см. листинг 5.13).
#include <sys/mman.h>
int posix_mem_offset (
const void *restrict addr,
size_t len, off_t *restrict off,
size_t *restrict contig_len,
int *restrict fildes);
Функция 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 (см. выше рис. 5.1), требуется зарезервировать блок памяти из пула 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, получить непрерывный мегабайтный массив.
Современные системы имеют сложную аппаратную организацию. Отметим следующие особенности таких систем, существенные для приложений реального времени.
Подкачка страниц, непопадание в
Если реализация не поддерживает виртуальной памяти, то описываемые далее функции могут просто ничего не делать (кроме, быть может, проверки корректности аргументов).
Для mlock() (см. листинг 5.14), воспользоваться которой может только процесс, обладающий соответствующими привилегиями (см. курс [1], где раскрывается понятие "соответствующие привилегии").
#include <sys/mman.h> int mlock (const void *addr, size_t len);
После успешного завершения вызова mlock() резидентными в памяти станут все страницы, пересекающиеся с частью addr и имеющей длину len байт. Реализация может требовать, чтобы значение addr указывало на границу страницы.
Если некоторая страница несколько раз отображена в
Удержание отменяется после вызовов fork() и exec(), а также ликвидации по какой-либо причине соответствующей части munlock() (см. листинг 5.15).
#include <sys/mman.h> int munlock (const void *addr, size_t len);
Более точно, munlock() разрешает больше не удерживать в памяти заданные страницы из адресного пространства вызывающего процесса. Если страница была отображена несколько раз и удерживалась в памяти несколькими вызовами mlock(), то одного обращения к munlock() для
Если нужно удерживать в памяти все mlockall() и munlockall() (см. листинг 5.16).
#include <sys/mman.h> int mlockall (int flags); int munlockall (void);
Поскольку flags функции mlockall(), в значении которого могут фигурировать следующие флаги.
MCL_CURRENT
Удерживать в памяти страницы, присутствующие в
MCL_FUTURE
Удерживать в памяти страницы, которые будут отображены в
Очевидно, установка обоих флагов позволяет удерживать в памяти и текущие, и будущие страницы.
Если установлен флаг MCL_FUTURE, то со временем общий объем удерживаемых страниц может превысить размеры
Функция munlockall() отменяет удержание для всех страниц, присутствующих в mlockall() не был установлен флаг MCL_FUTURE.
Определенную проблему составляет MCL_FUTURE при обращении к mlockall() и последующем вызове вспомогательной функции, в которой продекларирован автоматический массив достаточного размера (см. листинг 5.17).
/* * * * * * * * * * * * * * * * * * * * * * */
/* Программа демонстрирует удержание в памяти*/
/* всего адресного пространства процесса, */
/* в том числе стека с учетом возможного роста*/
/* * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
#include <sys/mman.h>
#define LOCKED_STACK_SIZE 048576
/* * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Следующая функция нужна, чтобы вызвать рост стека до*/
/* требуемого размера. На всякий случай примем меры для*/
/* защиты от слишком умного оптимизирующего компилятора*/
/* * * * * * * * * * * * * * * * * * * * * * * * * * * */
static int dummy_func (void) {
char dummy_arr [LOCKED_STACK_SIZE];
int i;
int res = 0;
dummy_arr [0] = 0;
for (i = 1; i < LOCKED_STACK_SIZE; i++) {
dummy_arr [i] = dummy_arr [i - 1] + i;
}
for (i = 0; i < LOCKED_STACK_SIZE; i++) {
res += dummy_arr [i];
}
return (res);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * */
/* Функция main() вызывает вспомогательную функцию */
/* * * * * * * * * * * * * * * * * * * * * * * * * */
int main (void) {
if (mlockall (MCL_CURRENT | MCL_FUTURE) != 0) {
perror ("MLOCKALL");
return (1);
}
fprintf (stderr, "Результат вспомогательной функции:"
"%d\n", dummy_func ());
return 0;
}
При написании такого рода программ следует позаботиться о защите от слишком интеллектуального оптимизирующего компилятора, который не только способен избавиться от неиспользуемых локальных переменных, но и может не включать в выходной код ненужные функции.
Идея, лежащая в основе рассматриваемого класса средств, в сущности, весьма проста. Если отобразить объект в
Отображаться могут
Реализация должна обеспечивать, чтобы отображению подвергалось целое число PAGESIZE. Отображаемая часть объекта должна начинаться с границы страницы. Длина отображаемой части не обязана быть кратной странице.
Попытки доступа к отображенной памяти, лежащей за текущей границей объекта, приводят к SIGBUS.
К отображенным страницам могут предоставляться различные SIGSEGV.
mmap() (см. листинг 5.1).
#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() (см. листинг 5.2).
#include <sys/mman.h>
int mprotect (void *addr,
size_t len, int prot);
Виды доступа меняются в соответствии со значением аргумента prot для всех страниц, пересекающихся с частью addr (которое должно указывать на границу страницы) и имеющей длину len байт.
Нормальным результатом функции mprotect() (и описываемых далее функций) служит нуль; в случае ошибки возвращается -1.
Для munmap() (см. листинг 5.3).
#include <sys/mman.h> int munmap (void *addr, size_t len);
Отображения отменяются для страниц, пересекающихся с частью addr (которое должно указывать на границу страницы) и имеющей длину len байт.
cat (см. листинг 5.4).
/* * * * * * * * * * * * * * * * * * * * * * * */
/* Программа выдает на стандартный вывод файлы,*/
/* имена которых заданы в командной строке */
/* * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
int main (int argc, char *argv []) {
struct stat sbuf; /* Структура для получения информации */
/* о файле */
int fd; /* Дескриптор обрабатываемого файла */
void *maddr; /* Адрес отображенного в память файла */
int i;
for (i = 1; i < argc; i++) {
if ((fd = open (argv [i], O_RDONLY)) < 0) {
perror ("OPEN");
continue;
}
if (fstat (fd, sbuf) != 0) {
perror ("FSTAT");
close (fd);
continue;
}
if ((maddr = mmap (NULL, sbuf.st_size,
PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED){
perror ("MMAP");
close (fd);
continue;
}
if (write (STDOUT_FILENO, maddr,
sbuf.st_size) != sbuf.st_size) {
perror ("WRITE");
}
if (munmap (maddr, sbuf.st_size) != 0) {
perror ("MUNMAP");
}
close (fd);
} /* for */
return 0;
}
Отметим, что обрабатываемые файлы только открываются, но не читаются, а отображаются в память, откуда и выдаются на стандартный вывод. Роль буфера играют отображенные страницы.
При работе с объектами в памяти полезны функции truncate() и, особенно, ftruncate() (см. листинг 5.5), позволяющие установить размер объекта равным заданной величине length. Напомним, что попытка записи за пределы отображенного объекта не только не расширит его, но, весьма вероятно, приведет к доставке процессу сигнала SIGBUS.
#include <unistd.h>
int truncate (const char *path,
off_t length);
int ftruncate (int fildes, off_t length);
Функции truncate() и ftruncate() применимы к ftruncate() применима к
Применение mmap() и ftruncate() иллюстрируется модифицированным вариантом двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод (см. листинги 5.6, 5.7, 5.8). Как и для исходного варианта, предполагается, что заголовочный файл называется "g_shm.h", а файл с образом процесса, запускаемого посредством .
#ifndef g_SHM #define g_SHM /* Имя объекта в разделяемой памяти */ #define O_SHM_NAME "/g_o.shm" /* Номер сигнала для синхронизации /* доступа к разделяемому сегменту */ #define SIG_SHM SIGALRM #endif
/* * * * * * * * * * * * * * * * * * * * * */
/* Программа, состоящая из двух процессов, */
/* копирует строки со стандартного ввода на*/
/* стандартный вывод, используя в качестве */
/* буфера разделяемый сегмент, отображенный*/
/* в адресные пространства процессов. */
/* Для синхронизации доступа к разделяемому*/
/* сегменту используется сигнал SIGALRM */
/* * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/wait.h>
#include <assert.h>
#include "g_shm.h"
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Создание разделяемого сегмента памяти, */
/* отображение его в адресное пространство процесса, */
/* чтение строк со стандартного ввода в сегмент */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
int main (void) {
int fd_shm; /* Дескриптор объекта в разделяемой памяти */
void *addr_shm; /* Адрес отображенного в память объекта */
sigset_t smask; /* Маска блокируемых сигналов */
siginfo_t sinfo;/* Структура для получения данных о сигнале */
/* Длительность ожидания прихода сигнала */
struct timespec stmspc = {10, 0};
pid_t cpid; /* Идентификатор порожденного процесса */
/* Создадим разделяемый сегмент памяти */
if ((fd_shm = shm_open (O_SHM_NAME, O_RDWR | O_CREAT,
0777)) < 0) {
perror ("SHM_CREAT");
return (1);
}
/* Сделаем размер созданного объекта равным LINE_MAX */
if (ftruncate (fd_shm, LINE_MAX) != 0) {
perror ("FTRUNCATE");
return (2);
}
/* Отобразим сегмент в память */
if ((addr_shm = mmap (NULL, LINE_MAX,
PROT_READ | PROT_WRITE, MAP_SHARED,
fd_shm, 0)) == MAP_FAILED) {
perror ("MMAP-P");
return (3);
}
/* Сформируем маску сигналов (блокируем SIG_SHM) */
(void) sigemptyset (smask);
(void) sigaddset (smask, SIG_SHM);
(void) sigprocmask (SIG_BLOCK, smask,
(sigset_t *) NULL);
/* Подготовительная работа закончена */
switch (cpid = fork ()) {
case -1:
perror ("FORK");
return (4);
case 0:
/* Чтение из объекта и выдачу на стандартный*/
/* вывод реализуем в порожденном процессе*/
if (execl ("./g_r_shm", "g_r_shm", (
char *) NULL) < 0) {
perror ("EXECL");
return (5);
}
}
/* Чтение строк со стандартного ввода в объект */
/* возложим на родительский процесс. */
/* В начальный момент объект в разделяемой памяти */
/* доступен для записи*/
while (fgets (addr_shm, LINE_MAX, stdin) != NULL) {
/* Сообщим порожденному процессу, */
/* что в объект в разделяемой памяти */
/* помещена очередная строка */
assert (kill (cpid, SIG_SHM) == 0);
/* Дождемся, когда в объект можно будет прочитать*/
/* следующую строку */
if (sigtimedwait (smask,
sinfo, stmspc) != SIG_SHM) {
break;
}
}
/* Порожденный процесс должен завершиться*/
/* по контролю времени ожидания*/
(void) wait (NULL);
if (shm_unlink (O_SHM_NAME) != 0) {
perror ("SHM_UNLINK");
return (6);
}
return (0);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Процесс берет строки из объекта в разделяемой памяти*/
/* и выдает их на стандартный вывод */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <limits.h>
#include <assert.h>
#include "g_shm.h"
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Открытие разделяемого сегмента памяти, */
/* отображение его в адресное пространство процесса, */
/* чтение из сегмента и выдача строк на стандартный вывод */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
int main (void) {
int fd_shm; /* Дескриптор объекта в разделяемой */
/* памяти */
void *addr_shm; /* Адрес отображенного в память */
/* объекта */
sigset_t smask; /* Маска ожидаемых сигналов */
siginfo_t sinfo; /* Структура для получения данных*/
/* о сигнале */
/* Длительность ожидания строки */
struct timespec stmspc = {10, 0};
pid_t ppid; /* Идентификатор родительского процесса */
/* Откроем разделяемый сегмент памяти */
if ((fd_shm = shm_open (O_SHM_NAME, O_RDONLY,
0777)) < 0) {
perror ("SHM_OPEN");
return (1);
}
/* Отобразим сегмент в память */
if ((addr_shm = mmap (NULL, LINE_MAX, PROT_READ,
MAP_SHARED, fd_shm, 0)) == MAP_FAILED) {
perror ("MMAP-C");
return (2);
}
/* Запомним идентификатор родительского процесса */
ppid = getppid ();
/* Сформируем маску ожидаемых сигналов (SIG_SHM) */
(void) sigemptyset (smask);
(void) sigaddset (smask, SIG_SHM);
/* Подготовительная работа закончена */
fputs ("Вводите строки\n", stdout);
while (sigtimedwait (smask, sinfo, stmspc) == SIG_SHM) {
/* Дождались, когда в объекте появилась строка.*/
/* Выдадим ее на стандартный вывод*/
assert (fputs ("Вы ввели: ", stdout) != EOF);
assert (fputs (addr_shm, stdout) != EOF);
/* Сообщим родительскому процессу, */
/* что данные из объекта извлечены */
assert (kill (ppid, SIG_SHM) == 0);
}
return 0;
}
В модифицированном варианте сигналы (не имеет значения - обычные или реального времени) применяются только как средство синхронизации. Для выявления конца файла задействован механизм контроля
Отметим, что при использовании MAP_SHARED в вызове mmap() обеспечивает доступность данных, записанных в объект одним процессом, всем другим процессам-читателям.
Еще одна возможность, полезная в связи с msync() (см. листинг 5.9).
#include <sys/mman.h>
int msync (void *addr,
size_t len, int flags);
Функция msync() записывает в долговременную память все измененные страницы объекта в памяти, пересекающиеся с фрагментом адресного пространства процесса, начинающимся с адреса addr (это должна быть граница страницы) и имеющим длину len байт.
Если объектом является файл, отображенный в память (естественно, без флага MAP_PRIVATE ), то вызов msync() гарантирует завершение всех операций записи с обеспечением msync() к
Аргумент flags определяет характер действий при записи измененных страниц. Флаг MS_ASYNC означает MS_SYNC - MS_INVALIDATE предписывает выполнить
Отметим, что функция msync() обязана присутствовать только на системах, поддерживающих две необязательные возможности стандарта POSIX-2001 - файлы, отображенные в память, и
Вообще говоря, вызов msync() - не единственная причина, способная инициировать запись в долговременную память; подобная запись может стать следствием нормальной системной активности.
Проиллюстрируем работу с файлами, отображенными в память, еще одним примером, реализующим некоторые функции систем управления базами данных в памяти (см. листинг 5.10).
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа реализует некоторые функции систем управления базами*/
/* данных в памяти, следуя принципу неуничтожения информации */
/* (изменения записываются не в сам объет, а в его новую версию) */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
#include s<ys/mman.h>
#include <assert.h>
/* Размер страницы */
static int pgsz;
/* Структура для получения */
/* информации об исходном файле*/
static struct stat sbuf_src;
/* Адрес отображенного в память исходного файла*/
static char *saddr;
/* Признак того, что выполнялась функция */
/* обработки сигнала SIGSEGV */
static char sigsegv_catching;
/* * * * * * * * * * * * * * * * * * * * */
/* Функция обработки сигнала SIGSEGV. */
/* Предполагается, что этот сигнал */
/* генерируется при попытке записи в */
/* страницы, доступные только на чтение. */
/* Функция добавляет разрешение на запись*/
/* * * * * * * * * * * * * * * * * * * * */
static void sigsegv_sigaction (int sig, siginfo_t *sig_info,
void *addr) {
void *page_addr; /* Адрес страницы, в которую*/
/*пытались писать*/
page_addr = (char *) sig_info->si_addr - (off_t)
((char *) sig_info->si_addr - saddr) % pgsz;
assert (mprotect (page_addr, pgsz,
PROT_READ | PROT_WRITE) == 0);
sigsegv_catching = 1;
}
/* * * * * * * * * * * * * * * * * * * * * * * */
/* Установка способа обработки сигнала SIGSEGV */
/* * * * * * * * * * * * * * * * * * * * * * * */
static int sigsegv_set_proc (void (*sigsegv_actn) (int,
siginfo_t *, void *), struct sigaction *old_sigsegv_sact) {
struct sigaction sact;
(void) sigemptyset (sact.sa_mask);
sact.sa_flags = SA_SIGINFO;
sact.sa_sigaction = sigsegv_actn;
if (sigaction (SIGSEGV, sact, old_sigsegv_sact) != 0) {
perror ("SIGACTION");
return (-1);
}
return 0;
}
/* * * * * * * * * * * * * * * * * * * * * */
/* Функция начала работы с исходным файлом.*/
/* Нормальный результат равен нулю */
/* * * * * * * * * * * * * * * * * * * * * */
static int init_src (char *name_src) {
int fd_src; /* Дескриптор исходного файла */
/* Откроем исходный файл и отобразим его в память */
if ((fd_src = open (name_src, O_RDONLY)) < 0) {
perror ("OPEN-SRC");
return (-1);
}
if (fstat (fd_src, sbuf_src) != 0) {
perror ("FSTAT");
return (-1);
}
if ((saddr = (char *) mmap (NULL, sbuf_src.st_size,
PROT_READ, MAP_PRIVATE, fd_src, 0)) == MAP_FAILED) {
perror ("MMAP");
return (-1);
}
return 0;
}
/* * * * * * * * * * * * * * * * * * */
/* Опрос конфигурационных параметров */
/* * * * * * * * * * * * * * * * * * */
static int init_conf_params (void) {
if ((pgsz = sysconf (_SC_PAGESIZE)) == -1) {
perror ("SYSCONF");
return (-1);
}
return 0;
}
/* * * * * * * * * * * */
/* Общая инициализация */
/* * * * * * * * * * * */
static int init_all (char *name_src) {
if ((init_src (name_src) != 0) || (sigsegv_set_proc
(sigsegv_sigaction, (struct sigaction *) NULL) != 0) ||
(init_conf_params () != 0)) {
return (-1);
}
return 0;
}
/* * * * * * * * * * * * * * * * * */
/* Запись в файл измененных страниц*/
/* * * * * * * * * * * * * * * * * */
static int write_dlt (char *name_dlt) {
int fd_dlt; /* Дескриптор файла изменений*/
volatile char tmp; /* Значение пробного байта*/
off_t pos;/* Позиция в отображенной памяти*/
if ((fd_dlt = open (name_dlt,
O_CREAT | O_WRONLY | O_TRUNC, 0777)) < 0) {
perror ("OPEN-DLT");
return (-1);
}
/* Измененные страницы выявим путем пробных записей. */
for (pos = 0; pos < sbuf_src.st_size; pos += pgsz) {
tmp = saddr [pos];
sigsegv_catching = 0;
saddr [pos] = tmp;
if (sigsegv_catching == 0) {
/* В страницу удалось записать */
/* без генерации сигнала SIGSEGV.*/
/* Значит, она была доступна на запись.*/
/* Следовательно, ее модифицировали.*/
/* Запишем ее в файл изменений */
(void) lseek (fd_dlt, pos, SEEK_SET);
assert (write (fd_dlt, saddr + pos, pgsz) == pgsz);
}
/* Уберем добавленное разрешение на запись */
assert (mprotect (saddr + pos, pgsz,
PROT_READ) == 0);
} /* for */
return (close (fd_dlt));
}
/* * * * * * * * * * * * * */
/* Функция main() вызывает */
/* описанные выше функции */
/* и обращается на запись */
/* к отображенной памяти */
/* * * * * * * * * * * * * */
int main (int argc, char *argv []) {
if (argc != 3) {
fprintf (stderr, "Использование: %s исходный_файл "
"файл_изменений\n", argv [0]);
return (-1);
}
if (init_all (argv [1]) != 0) {
fprintf (stderr, "Ошибка инициализации\n");
return (-1);
}
printf ("Размер страницы: %d\n"
"Адрес отображенного в память исходного файла: %p\n"
"Длина отображенного в память исходного файла: %ld\n",
pgsz, saddr, sbuf_src.st_size);
saddr [0] = '*';
return (write_dlt (argv [2]));
}
Приведенная программа следует фундаментальному принципу неуничтожения информации. При модификациях порождаются новые версии объектов; первоначальные объекты остаются неизменными. Подобный подход особенно полезен, когда СУБД является технологическим элементом инструментальной среды разработки программ, поскольку историю создания и модификации программных систем обязательно нужно сохранять.
Обратим внимание на изменение разрешенных mprotect(). Первоначально отображенная память доступна только на чтение. При попытке записи в отображенную память генерируется сигнал SIGSEGV, и функция его обработки добавляет доступ на запись к соответствующей странице. В результате появляется возможность отслеживания изменений и сохранения в последующем только измененных страниц, так что дисковое пространство на версии объектов будет расходоваться экономно.
Выбранный способ выявления измененных страниц путем пробных записей может показаться несколько вычурным (в SIGSEGV можно было бы формировать список адресов страниц, в которые производится запись, и тогда в функции фиксации изменений достаточно просто пройти по этому списку), но он вытекает из еще одного важного принципа - в максимальной степени упрощать и ускорять обычную работу, даже за счет возможного усложнения и замедления редко исполняемых фрагментов.
К сожалению, приведенная программа не вполне соответствует стандарту POSIX-2001 в части обработки сигналов. По стандарту поведение процесса после нормального выхода из функций обработки сигналов SIGBUS, SIGFPE, SIGILL и SIGSEGV не определено, если только последние не были сгенерированы вызовами kill(), sigqueue() или raise(). (Заметим попутно, что и игнорирование перечисленных сигналов приводит к неопределенному поведению.) Строго говоря, и вызов mprotect() из функций обработки сигналов не считается безопасным. В общем, функция sigsegv_sigaction() - сама нестандартность и немобильность, и это, конечно, плохо, но то, что вся нестандартность и немобильность программы сосредоточена в одной небольшой функции, безусловно, хорошо.
(рис 5.1) Пример многопроцессорной системы с несколькими пулами памяти
Здесь M4 доступен только процессору P2. Пул M2 включает в себя пулы M2.1 и M2.2, причем доступ к M2.1 возможен только через B1. Остальные пулы доступны обоим процессорам через указанные на рисунке
Разные
Каждая допустимая комбинация malloc(), когда приложение задает только запрашиваемый объем), так и
posix_typed_mem_open() (см. листинг 5.11)
#include <sys/mman.h> int posix_typed_mem_open (const char *name, int oflag, int tflag);
Нормальным результатом функции 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() (см. листинг 5.12) можно выяснить максимальный объем памяти, доступной для резервирования. Это важно, поскольку
#include <sys/mman.h> int posix_typed_mem_get_info (int fildes, struct posix_typed_mem_info *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() (см. листинг 5.13).
#include <sys/mman.h>
int posix_mem_offset (
const void *restrict addr,
size_t len, off_t *restrict off,
size_t *restrict contig_len,
int *restrict fildes);
Функция 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 (см. выше рис. 5.1), требуется зарезервировать блок памяти из пула 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, получить непрерывный мегабайтный массив.
Современные системы имеют сложную аппаратную организацию. Отметим следующие особенности таких систем, существенные для приложений реального времени.
Подкачка страниц, непопадание в
Если реализация не поддерживает виртуальной памяти, то описываемые далее функции могут просто ничего не делать (кроме, быть может, проверки корректности аргументов).
Для mlock() (см. листинг 5.14), воспользоваться которой может только процесс, обладающий соответствующими привилегиями (см. курс [1], где раскрывается понятие "соответствующие привилегии").
#include <sys/mman.h> int mlock (const void *addr, size_t len);
После успешного завершения вызова mlock() резидентными в памяти станут все страницы, пересекающиеся с частью addr и имеющей длину len байт. Реализация может требовать, чтобы значение addr указывало на границу страницы.
Если некоторая страница несколько раз отображена в
Удержание отменяется после вызовов fork() и exec(), а также ликвидации по какой-либо причине соответствующей части munlock() (см. листинг 5.15).
#include <sys/mman.h> int munlock (const void *addr, size_t len);
Более точно, munlock() разрешает больше не удерживать в памяти заданные страницы из адресного пространства вызывающего процесса. Если страница была отображена несколько раз и удерживалась в памяти несколькими вызовами mlock(), то одного обращения к munlock() для
Если нужно удерживать в памяти все mlockall() и munlockall() (см. листинг 5.16).
#include <sys/mman.h> int mlockall (int flags); int munlockall (void);
Поскольку flags функции mlockall(), в значении которого могут фигурировать следующие флаги.
MCL_CURRENT
Удерживать в памяти страницы, присутствующие в
MCL_FUTURE
Удерживать в памяти страницы, которые будут отображены в
Очевидно, установка обоих флагов позволяет удерживать в памяти и текущие, и будущие страницы.
Если установлен флаг MCL_FUTURE, то со временем общий объем удерживаемых страниц может превысить размеры
Функция munlockall() отменяет удержание для всех страниц, присутствующих в mlockall() не был установлен флаг MCL_FUTURE.
Определенную проблему составляет MCL_FUTURE при обращении к mlockall() и последующем вызове вспомогательной функции, в которой продекларирован автоматический массив достаточного размера (см. листинг 5.17).
/* * * * * * * * * * * * * * * * * * * * * * */
/* Программа демонстрирует удержание в памяти*/
/* всего адресного пространства процесса, */
/* в том числе стека с учетом возможного роста*/
/* * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
#include <sys/mman.h>
#define LOCKED_STACK_SIZE 048576
/* * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Следующая функция нужна, чтобы вызвать рост стека до*/
/* требуемого размера. На всякий случай примем меры для*/
/* защиты от слишком умного оптимизирующего компилятора*/
/* * * * * * * * * * * * * * * * * * * * * * * * * * * */
static int dummy_func (void) {
char dummy_arr [LOCKED_STACK_SIZE];
int i;
int res = 0;
dummy_arr [0] = 0;
for (i = 1; i < LOCKED_STACK_SIZE; i++) {
dummy_arr [i] = dummy_arr [i - 1] + i;
}
for (i = 0; i < LOCKED_STACK_SIZE; i++) {
res += dummy_arr [i];
}
return (res);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * */
/* Функция main() вызывает вспомогательную функцию */
/* * * * * * * * * * * * * * * * * * * * * * * * * */
int main (void) {
if (mlockall (MCL_CURRENT | MCL_FUTURE) != 0) {
perror ("MLOCKALL");
return (1);
}
fprintf (stderr, "Результат вспомогательной функции:"
"%d\n", dummy_func ());
return 0;
}
При написании такого рода программ следует позаботиться о защите от слишком интеллектуального оптимизирующего компилятора, который не только способен избавиться от неиспользуемых локальных переменных, но и может не включать в выходной код ненужные функции.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.