Для создания сегментов разделяемой памяти используется механизм отображаемых файлов mmap. Выполнение системного вызова mmap
с параметрами MAP_SHARED
и указанием файлового дескпритора позволяет использовать некоторое имя в файловой системе для взаимодействия между собой неродственных процессов.
Для того, чтобы избежать создания файлов на диске (которые занимают место), предусмотрен механизм создания ортогонального пространства имен ("ключей") для разделяемых файлов, которые существуют только в памяти.
Каждый ключ - это некоторая строка длиной до NAME_MAX
байт, которая должна начинаться с символа '/'
. У ключей могут быть права доступа и владелец, по аналогии с обычными файлами.
Такие разделяемые объекты существуют до перезагрузки компьютера, либо до их явного удаления одним из процессов.
int shm_open(const char *name, int flags, mode_t mode)
- по аналогии с системным вызовомopen
, открывает ключ по имени, как обычный файл. Если среди флагов вflags
присутствует опцияO_CREAT
, то третий аргумент подразумевает права доступа, в противном случае его значение игнорируется. Возвращает файловый дескриптор, который можно передать в системный вызовmmap
.int shm_unlink(const char *path)
- удаляет имя из памяти. Если разделяемый файл был имеет отображение в одном или нескольких процессах, то он продолжает быть доступным и занимать место в памяти.
Права доступа можно настраивать с помощью системных вызовов fchmod
и fchown
, которые, в отличии от команд shell'а и системных вызовов, работающих с именами, позволяют работать с файловыми дескрипторами.
При создании объекта, он имеет размер 0
, и может быть изменен с помощью ftruncate
.
В операционной системе Linux (в отличии, например, от FreeBSD), объекты разделяемой памяти - это самые обычные файлы, которые располагаются в файловой системе tmpfs
, примонтированной в /dev/shm
.
Кроме того, есть дополнительные особенности:
- для использования функций разделяемых объектов POSIX, нужно указывать опцию
-lrt
, посколькоglibc
разбита на несколько частей; - требование про символ
/
в начале имени ключа является не обязательным; тем не менее, это противоречит стандартуPOSIX
и в BSD системах приводит к ошибкеEINVAL
, а в системеQNX
просто создаст обычный файл на диске в текущем каталоге.
Семафор - это беззнаковая целочисленная переменная, которая обладает дополнительными свойтсвами:
- существуют операции увеличения и уменьшения счетчика, которые выполняются атомарно;
- при попытке уменьшить счетчик, который равен
0
, выполняется приостановка процесса (или нити), который пытается это сделать; - при увеличении счетчика, если существует какой-то приостановленный процесс, который пытался его уменьшить, работа этого процесса возобновляется.
В теоретической литературе операция захвата семафора (уменьшения значения) обычно обозначается буквой P
(proberen), а операция освобождения (увеличение значения) - буквой V
(verhogen), - названия операций заимствованы из нидерландского языка, т.к. семафоры изобрел Дейкстра.
Семафоры часто используют для синхронизации между собой нескольких потоков выполнения, и в частности, они могут использоваться для предотвращения гонки данных.
Семафоры POSIX определяются некотроым типом sem_t
, объявленным в файле <semaphore.h>
, реализация которого, в общем случае, считается неопределенной, и зависит не только от конкретной операционной системы, но и от процессора.
Функции работы с семафорами обычно принимают его по указателю:
sem_wait(sem_t *sem)
- захватить семафор (операцияP
);sem_post(sem_t *sem)
- освободить семафор (операцияV
);sem_trywait(sem_t *sem)
- попытаться захватить семафор, если он равен нулю, то процесс не блокируется, а функция вовзращает значение-1
, прописывая значениеEAGAIN
вerrno
;sem_timedwait(sem_t *sem, struct timespec *timeout)
- захватывает семафор, но если за указанный промежуток времени он не был разблокирован, то функция завершает свою работу с ошибкойETIMEDOUT
;sem_getvalue(sem_t *sem, int *out_var)
- читает численное значение семафора, не блокируя его; эта функция бывает полезна при отладке.
В системе Linux для использования функций работы с семафорами необходимо компоновать программу с опцией компилятора -pthread
.
Перед использованием, семафоры должны быть корректно инициализированы. Инициализиция зависит от типа семафора.
Анонимные семафоры - это семафоры, которые доступны только в рамках одного адресного пространства (для многопоточности), либо родственным процессам.
Создаются с попощью функции sem_init
:
int sem_init(sem_t *sem, // указатель на семафор в памяти
int pshared, // 0 - если предназначен для использования
// в рамках одного адресного пространства,
// 1 - если разными процессами
unsinged value // начальное значение
)
Уничтожаются анонимные семафоры с помощью функции sem_destroy
. При этом ситуация, когда какие-то процессы или нити были заблокированы семафором, считается неопределенным поведением, и может приводить к полной блокировке.
Семафоры, которые предназначены для использования разными процессами, должны находиться в разделяемой через mmap
области памяти, доступной всем задействованным процессам. В противном случае, изменения семафора в одном процессе не будут видны остальным.
Именованные семафоры - это реализация семафоров совместно с механизмом разделяемой памяти POSIX. Имена семафоров подчиняются тем же правилам, что имена сегментов разделяемой памяти, за одним исключением: максимальная длина имени на 4 байта короче, т.к. к имени семафора автоматически приписывается (в зависимости от реализации) суффикс .sem
или префикс sem.
.
Создаются именованные семафоры с помощью sem_open
:
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,
mode_t mode, unsigned int value);
По аналогии с open
, если присутствует флаг O_CREAT
, то нужно указать права доступа, и кроме того, - начальное значение. В отличии от обычных файлов, не нужно указывать флаги, определяющие режим открытия на чтение/запись. Если открывается существующий семафор, то oflag=0
.
Закрытие именованного семафора с помощью sem_close
, в отличии от анонимного, никак не влияет на процессы, которые могут быть им заблокированы: значение счетчика остается неизменным, и может быть изменено после повторного открытия.
Применять операцию sem_destroy
для именованных семафоров запрещено, так же как и операцию sem_close
- для анонимных.
Для удаления имени семафора используется функция sem_unlink(const char *name)
. Как и в случае обычного файла, значение семафора сохраняется даже после удаления до тех пор, пока он не будет закрыт всеми использующими его процессами.