Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Системное программирование в среде Linux. Учебное пособие
.pdf
value = 2 // значение после возвращения из sem_post
[gun@CentOS]$ ./semgetvalue /test1
value = 2
Именованные семафоры POSIX могут быть размещены не только в
виртуальной файловой системе, но и в отображаемом на память файле.
Отображаемые в память файлы описаны в следующем разделе.
По окончании работы с примерами не забудем удалить семафор:
[gun@CentOS]$ ./semunlink /test1
Result:Success
Убедимся визуально, что семафор удален:
[gun@CentOS]$ ls -l /dev/shm/sem.test1
ls: невозможно получить доступ
к
/dev/shm/sem.test1: Нет такого файла или каталога
Функции sem_init и sem_destroy
До сих пор обсуждались только именованные семафоры POSIX.
Они идентифицируются аргументом name, обычно представляющим
собой имя файла в файловой системе. Стандарт POSIX описывает также семафоры, размещаемые в памяти, память под которые (типа
sem_t) выделяет приложение, а инициализируются они системным
вызовом:
#include <semaphore.h>
int sem_init(sem_t *sem, int shared, unsigned int
value );
Функция возвращает 0
в случае успешного завершения или –1
в случае ошибки.
Размещаемый в памяти семафор инициализируется вызовом
sem_init. Аргумент sem указывает на переменную типа sem_t, место под которую должно быть выделено приложением. Если аргумент
shared равен 0, то семафор используется потоками одного процесса
(см. главу 7), в противном случае доступ к нему могут иметь
несколько
процессов. Если аргумент shared ненулевой, семафор должен быть
размещен в одном из видов разделяемой памяти и должен быть доступен всем процессам, использующим его. Как и в вызове sem_open,
аргумент value задает начальное значение семафора.
151

Обратите внимание, что для размещаемого в памяти семафора нет
ничего аналогичного флагу O_CREAT: функция sem_init всегда
инициализирует значение семафора. Следовательно, нужно быть внимательным, чтобы вызывать sem_init только один раз для каждого
семафора. При вызове sem_init для уже инициализированного семафора результат непредсказуем.
После завершения работы с размещаемым в памяти семафором
его
можно уничтожить, вызвав sem_destroy:
int sem_destroy(sem_t *sem );
Функция возвращает 0 в случае успешного завершения или –1
в случае ошибки.
Размещаемый в памяти семафор может быть использован в тех
случаях, когда нет необходимости использовать имя, связываемое с
именованным семафором. Именованные семафоры обычно используются для синхронизации работы неродственных процессов. Имя в этом
случае используется для
идентификации семафора.
Размещаемый в памяти семафор не утрачивает функциональности
до тех пор, пока память, в которой он размещен, еще доступна какомулибо процессу.
Если размещаемый в памяти семафор совместно используется потоками одного процесса (аргумент shared при вызове sem_init равен 0), семафор обладает живучестью процесса и удаляется при завершении
последнего.
Если размещаемый в памяти семафор совместно используется несколькими процессами (аргумент shared при вызове sem_init равен 1), он должен располагаться в разделяемой памяти, и в этом случае
семафор существует столько, сколько существует эта область памяти,
поскольку и разделяемая память POSIX, и разделяемая память System
V обладают живучестью ядра.
Ограничения на семафоры
Стандартом POSIX
определены два ограничения на семафоры:
SEM_NSEMS_MAX – максимальное количество одновременно открытых семафоров для одного процесса (POSIX требует, чтобы это
значение было не менее 256);
SEM_VALUE_MAX – максимальное значение семафора (POSIX
требует, чтобы оно было не меньше 32 767).
152

Две эти константы обычно определены в заголовочном файле
<unistd.h> и могут быть получены во время выполнения вызовом sys-
conf, как показано в файле semsysconf.c, доступном на сайте [6].
При запуске этой программы в системе CentOS 6.9 получим следующий результат:
[gun@CentOS]$ ./semsysconf
SEM_NSEMS_MAX = 256, SEM_VALUE_MAX = 32767
6.4.4. Разделяемая память POSIX IPC
Стандарт POSIX.1 предоставляет два механизма совместного использования областей памяти для неродственных процессов:
1. Отображение файлов в память: файл открывается вызовом open,
а его дескриптор используется при вызове функции mmap для отображения содержимого файла в адресное пространство процесса. Этот
метод позволяет реализовать совместное использование памяти как
для родственных, так и для
2. Объекты разделяемой памяти: функция shm_open открывает объект IPC с именем стандарта POSIX (например, полным именем объекта
файловой системы), возвращая дескриптор, который может быть использован для отображения в адресное пространство процесса вызовом mmap.
Оба метода требуют вызова mmap. Отличие состоит в методе получения дескриптора, являющегося аргументом mmap: в
он возвращается функцией open, а во втором – shm_open. Это иллюстрирует рис. 6.19. Стандарт POSIX называет объектами памяти
(memory objects) и отображенные в память файлы, и объекты разделяемой памяти стандарта POSIX.
неродственных процессов.
первом случае
Рис. 6.19. Объекты памяти POSIX: отображаемые в память
файлы и объекты разделяемой памяти
153

Поскольку оба подхода требуют использования функций для работы с отображаемыми на память файлами, вначале изучим их.
Функции mmap, munmap и msync
Функция mmap отображает в адресное пространство процесса файл
или объект разделяемой памяти POSIX. Мы используем эту функцию в
следующих ситуациях:
1. С обычными файлами для обеспечения ввода-вывода через отоб-
ражение в память.
2. Со специальными файлами для обеспечения неименованного
отображения памяти (несуществующие файлы, как в Windows API и
файл /dev/zero), но такие файлы поддерживаются только в ОС семейства BSD.
3. С функцией shm_open для создания участка разделяемой неродственными процессами памяти POSIX.
#include <sys/mman.h>
void *mmap(void *addr, size_t len, int prot,
int flags, int fd, off_t offset );
Функция возвращает начальный адрес участка памяти в случае
успешного завершения или MAP_FAILED в
случае ошибки.
Аргумент addr может указывать начальный адрес участка памяти
процесса, в который следует отобразить содержимое дескриптора fd.
Обычно ему присваивается значение нулевого указателя, что говорит
ядру о необходимости выбрать начальный адрес самостоятельно. В
любом случае функция возвращает начальный адрес сегмента памяти,
выделенной для отображения.
Аргумент len задает длину отображаемого участка
в байтах; участок может начинаться не с начала файла, а с некоторого места, задаваемого аргументом offset. Обычно offset = 0. На рис. 6.20 изображена схема отображения объекта в память.
Защита участка памяти с отображенным объектом обеспечивается с
помощью аргумента prot и констант, приведенных в табл. 6.13.
Обычное значение этого аргумента – PROT_READ | PROT_WRITE,
что обеспечивает доступ на чтение и запись.
154

Рис. 6.20. Пример отображения файла в память
Таблица 6.13
Аргумент prot для вызова mmap
prot
PROT_READ
PROT_WRITE
PROT_EXEC
PROT_NONE
Данные могут быть считаны
Данные могут быть записаны
Данные могут быть выполнены
Доступ к данным закрыт
Описание
Аргумент flags может принимать значения из табл. 6.14. Можно
указать только один из флагов – MAP_SHARED или MAP_PRIVATE,
прибавив к нему при необходимости MAP_FIXED. Если указан флаг
MAP_PRIVATE, все изменения будут производиться только с образом
объекта в адресном пространстве процесса, другим процессам они доступны не будут. Если же указан флаг MAP_SHARED, изменения
отображаемых данных видны всем процессам, совместно использующим
объект.
Для обеспечения переносимости пpoгрaмм флаг MAP_FIXED указывать не следует. Если он не указан, но аргумент addr представляет
собой ненулевой указатель, интерпретация этого аргумента зависит от
реализации. Ненулевое значение addr обычно трактуется как указатель на желаемую область памяти,
в которую нужно произвести отображение. В переносимой программе значение addr должно быть нулевым и флаг MAP_FIXED не должен быть указан.
155

Аргумент flag для вызова mmap
Таблица 6.14
flag
MAP SHARED
MAP_PRIVATE
MAP_FIXED
Изменения передаются другим процессам
Изменения не передаются другим процессам и не вли-
яют на отображенный объект
Аргумент addr интерпретируется как адрес памяти
Описание
Одним из способов добиться совместного использования памяти
родительским и дочерним процессами является вызов mmap с флагом
MAP_SHARED перед вызовом fork. Стандарт POSIX.1 гарантирует в
этом случае, что все отображения памяти, установленные родительским процессом, будут унаследованы дочерним. Более того, изменения
в содержимом объекта, вносимые родительским процессом, будут
видны дочернему процессу, и
наоборот.
Для отключения отображения объекта в адресное пространство
процесса используется вызов munmap:
#include <sys/mman.h>
int munmap(void *addr, size_t len );
Функция возвращает 0 в случае успешного завершения или –1 в
случае ошибки.
Аргумент addr должен содержать адрес, возвращенный mmap, a
len – длину области отображения. После вызова munmap любые попытки обратиться к этой области памяти приведут к отправке процессу
сигнала SIGSEGV
(предполагается, что эта область памяти не будет
снова отображена вызовом mmap).
Если область была отображена с флагом MAP_PRIVATE, все внесенные за время работы процесса изменения сбрасываются.
В изображенной на рис. 6.20 схеме ядро обеспечивает синхронизацию содержимого файла, отображенного в память, с самой памятью
при помощи алгоритма работы с виртуальной
памятью (если сегмент
был отображен с флагом MAP_SHARED). Если происходит изменение
содержимого ячейки памяти, в которую отображен файл, через некоторое время содержимое файла будет соответствующим образом изменено ядром. Однако в некоторых случаях необходимо, чтобы содер-
156

жимое файла всегда было в соответствии с содержимым памяти. Тогда
для осуществления моментальной синхронизации вызывают функцию
msync:
#include <sys/mman.h>
int msync(void *addr, size_t len , int flags );
Функция возвращает 0 в случае успешного завершения или –1 в
случае ошибки.
Аргумент flags представляет собой комбинацию констант из
табл. 6.15.
Таблица 6.15
Значения аргумента flags для функции msync
Константа Описание
MS_ASYNC
MS_SYNC
MS_INVALIDATE
Осуществлять асинхронную запись
Осуществлять синхронную запись
Сбросить кэш
Из двух констант MS_ASYNC и MS_SYNC указать нужно одну и
только одну. Отличие между ними в том, что возврат из функции при
указании флага MS_ASYNC происходит сразу же, как только данные
для записи будут помещены в очередь ядром, а при указании флага
MS_SYNC возврат происходит только после завершения операций записи. Если указан и флаг MS_INVALIDATE, все копии файла, содержимое которых не совпадает с его текущим содержимым, считаются
устаревшими. Последующие обращения к этим копиям приведут к
считыванию данных из файла.
Удобство работы с отображением в память содержимого файла состоит в том, что все операции ввода-вывода осуществляются ядром и
скрыты от программиста, а он просто пишет код, считывающий и записывающий данные в некоторую область памяти. Ему не приходится
вызывать read, write или lseek. Часто это заметно упрощает код.
Следует, однако, иметь в виду, что не все файлы могут быть отображены в память. Попытка отобразить дескриптор, указывающий на
терминал или сокет, приведет к возвращению ошибки при вызове
mmap. К дескрипторам этих типов доступ осуществляется только с помощью read и write (и аналогичных вызовов).
157

Другой целью использования mmap может являться разделение
памяти между неродственными процессами. В этом случае содержимое файла становится начальным содержимым разделяемой памяти и любые изменения, вносимые в нее процессами, копируются
обратно в файл (что дает этому виду IPC живучесть файловой системы). Предполагается, что при вызове mmap указывается флаг
MAP_SHARED, необходимый
для разделения памяти между процес-
сами.
Функции shm_open и shm_unlink
Процесс получения доступа к объекту разделяемой памяти POSIX
выполняется в два этапа:
1. Вызов shm_open с именем IPC в качестве аргумента позволяет
либо создать новый объект разделяемой памяти, либо открыть существующий.
2. Вызов mmap позволяет отобразить разделяемую память в адресное пространство вызвавшего процесса.
Аргумент name, указанный при первом вызове shm_open, должен
впоследствии использоваться всеми прочими процессами, желающими
получить доступ к данной области памяти.
Причина, почему этот процесс выполняется в два этапа вместо одного, на котором в ответ на имя объекта возвращался бы адрес соответствующей области памяти, заключается в том, что функция mmap
уже существовала, когда эта форма разделяемой памяти была включена в стандарт POSIX.
#include <sys/mman.h>
int shm_open(const char *name, int oflag, mode_t
mode );
Функция возвращает неотрицательный дескриптор в случае
успешного завершения или –1 в случае ошибки.
int shm_unlink(const char *name );
Функция возвращает 0 в случае успешного завершения или –1
в случае ошибки.
Аргумент oflag должен содержать флаг O_RDONLY либо O_RDWR
и один из следующих: O_CREAT, O_EXCL, O_TRUNC
. Флаги O_CREAT
и O_EXCL были описаны ранее. Если вместе с флагом O_RDWR указан
158

флаг O_TRUNC, существующий объект разделяемой памяти будет укорочен до нулевой длины.
Аргумент mode задает биты разрешений доступа (табл. 6.11) и используется только при указании флага O_CREAT. Обратите внимание,
что в отличие от функций mq_open и sem_open для shm_open аргумент mode указывается всегда. Если флаг O_CREAT не указан, значение
аргумента mode может быть нулевым.
Возвращаемое значение shm_open представляет собой целочисленный дескриптор, который может использоваться при вызове mmap в
качестве пятого аргумента.
Функция shm_unlink удаляет имя объекта разделяемой памяти.
Как и другие подобные функции (удаление файла из файловой системы, удаление очереди сообщений и именованного семафора POSIX),
она не выполняет
никаких действий до тех пор, пока объект не будет
закрыт всеми открывшими его процессами. Однако после вызова
shm_unlink последующие вызовы open, mq_open и sem_open
выполняться не будут.
Функции ftruncate и fstat
Размер файла или объекта разделяемой памяти можно изменить
вызовом ftruncate:
#include <unistd.h>
int ftruncate(int fd, off_t length );
Функция возвращает 0 в случае успешного завершения или –1
в случае ошибки.
Стандарт
POSIX делает некоторые различия в определении действия этой функции для обычных файлов и для объектов разделяемой
памяти.
1. Для обычного файла: если размер файла превышает значение
length, избыточные данные отбрасываются. Если размер файла
оказывается меньше значения length, действие функции не определено. Поэтому для переносимости следует использовать следующий способ увеличения
длины обычного файла: вызов seek со
сдвигом length-1 и запись одного байта в файл. К счастью, почти
все реализации UNIX поддерживают увеличение размера файла вызовом ftruncate.
159

2. Для объекта разделяемой памяти ftruncate устанавливает
размер объекта равным значению аргумента length.
Итак, ftruncate вызывается для установки размера только что
созданного объекта разделяемой памяти или изменения размера существующего объекта. При открытии существующего объекта разделяемой памяти следует воспользоваться fstat для получения информации о нем:
#include <sys/types.h>
#include <sys/stat.h>
int fstat(int fd, struct stat *buf );
Функция возвращает 0 в
случае успешного завершения или –1
в случае ошибки.
В структуре stat содержится больше десятка полей, но только четыре из них содержат актуальную информацию, если fd представляет
собой дескриптор области разделяемой памяти:
struct stat {
…
mode_t st_mode; /* mode: S_I{RW}{USR,GRP,OTH} */
uid_t st_uid; /* UID владельца */
gid_t st_gid; /* GID владельца */
off_t st_size; /* размер в байтах */
…
};
Пример использования этих двух функций приведен ниже.
Простые программы
Приведем несколько
примеров программ, работающих с разделяе-
мой памятью POSIX.
Программа shmcreate.c, текст которой доступен на сайте [6] в разделе «PosixShm», создает объект разделяемой памяти с указанным
именем и длиной. Вызов shm_open создает объект разделяемой памяти. Когда указан параметр –е, будет возвращена ошибка в том случае,
если такой объект уже существует. Вызов ftruncate устанавливает
длину (размер объекта), a mmap отображает его содержимое в адресное
пространство процесса. Затем программа завершает работу. Поскольку
160
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
