Мы с вами говорили, что далее речь пойдет о разделяемых ресурсах, доступ к которым может осуществляться со стороны произвольных процессов, в общем случае, в произвольном порядке. Эти ресурсы доступны любому процессу, а процессы не обязательно должны быть родственными. При наличии такой схемы возникают две принципиальные проблемы:
1. Именование;
2. Синхронизация;
Проблемы именования связаны с тем, что родственных связей нет и по наследству передать ничего нельзя.
Если проблема именования решена, то возникает проблема синхронизации доступа - как организовать обмен с ресурсами, чтобы этот обмен был корректным. Если у нас есть, например, ресурс “оперативная память”, то когда один процесс еще не дописал информацию, а другой процесс уже прочитал весь блок, то возникает некорректная ситуация.
Решения этих проблем мы и будем рассматривать.
Проблема именования решается за счет ассоциирования с каждым ресурсом некоторого ключа. В общем случае это целочисленное значение. То есть при создании разделяемого ресурса его автор приписывает ему номер и определяет права доступа к этому ресурсу. После этого любой процесс, который укажет системе, что он хочет общаться с разделяемым ресурсом с ключом N, и обладает необходимыми правами доступа, будет допущен для работы с этим ресурсом.
Однако такое решение не является идеальным, так как вполне возможна коллизия номеров - когда совпадают номера разделяемых ресурсов. В этом случае процессы будут путаться, что неизбежно приведет к ошибкам. Поэтому в системе предусмотрено стандартное средство генерации уникальных ключей. Для генерации уникального ключа используется функция ftok
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(char *s, char c);
Суть ее действия - по текстовой строке и символу генерируется уникальное для каждой такой пары значение ключа. После этого сгенерированным ключом можно пользоваться как для создания ресурса, так и для подтверждения использования ресурса. Более того, для исключения коллизий, рекомендуется указывать в качестве параметра "указателя на строку" путь к некоторому своему файлу. Второй аргумент - символьный, который позволяет создавать некоторые варианты ключа, связанного с этим именем, этот аргумент называется проектом (project). При таком подходе можно добиться отсутствия коллизий.
Давайте посмотрим конкретные средства работы с разделяемыми ресурсами.
Общая схема работы с разделяемыми ресурсами такова - есть некоторый процесс-автор, создающий ресурс с какими-либо параметрами. При создании ресурса разделяемой памяти задаются три параметра - ключ, права доступа и размер области памяти. После создания ресурса к нему могут быть подключены процессы, желающие работать с этой памятью. Соответственно, имеется действие подключения к ресурсу с помощью ключа, который генерируется по тем же правилам, что и ключ для создания ресурса. Понятно, что здесь имеется момент некоторой рассинхронизации, который связан с тем, что потребитель разделяемого ресурса (процесс, который будет работать с ресурсом, но не является его автором) может быть запущен и начать подключаться до запуска автора ресурса. В этой ситуации особого криминала нету, так как имеются функции управления доступом к разделяемому ресурсу, с использованием которых можно установить некоторые опции, определяющие правила работы функций, взаимодействующих с разделяемыми ресурсами. В частности, существует опция, заставляющая процесс дождаться появления ресурса. Это также, может быть, не очень хорошо, например, автор может так и не появиться, но другого выхода нету, это есть некоторые накладные расходы. Вот в общих словах - что есть что.
Давайте рассмотрим те функции, которые предоставляются нам для работы с разделяемыми ресурсами.
Первая функция - создание общей памяти.
int shmget (key_t key, int size, int shmemflg);
key - ключ разделяемой памяти
size - размер раздела памяти, который должен быть создан
shmemflg - флаги
Данная функция возвращает идентификатор ресурса, который ассоциируется с созданным по данному запросу разделяемым ресурсом. То есть в рамках процесса по аналогии с файловыми дескрипторами каждому разделяемому ресурсу определяется его идентификатор. Надо разделять ключ - это общесистемный атрибут, и идентификатор, используя который мы работаем с конкретным разделяемым ресурсом в рамках процесса.
С помощью этой функции можно как создать новый разделяемый ресурс “память” (в этом случае во флагах должен быть указан IPC_CREAT)?, а также можно подключиться к существующему разделяемому ресурсу. Кроме того, в возможных флагах может быть указан флаг IPC_EXECL, он позволяет проверить и подключиться к существующему ресурсу - если ресурс существует, то функция подключает к нему процесс и возвращает код идентификатора, если же ресурс не существует, то функция возвращает -1 и соответствующий код в errno.
Следующая функция - доступ к разделяемой памяти:
char *shmat(int shmid, char *shmaddr, int shmflg);
shmid - идентификатор разделяемого ресурса
shmaddr - адрес, с которого мы хотели бы разместить разделяемую память
При этом, если значение shmaddr - адрес, то память будет подключена, начиная с этого адреса, если его значение - нуль, то система сама подберет адрес начала. Также в качестве значений этого аргумента могут быть некоторые предопределенные константы, которые позволяют организовать, в частности выравнивание адреса по странице или началу сегмента памяти.
shmflg - флаги. Они определяют разные режимы доступа, в частности, есть флаг SHM_RDONLY.
Эта функция возвращает указатель на адрес, начиная с которого будет начинаться запрашиваемая разделяемая память. Если происходит ошибка, то возвращается -1.
Хотелось бы немного поговорить о правах доступа. Они реально могут использоваться и корректно работать не всегда. Так как, если аппаратно не поддерживается закрытие области данных на чтение или на запись, то в этом случае могут возникнуть проблемы с реализацией такого рода флагов. Во-первых, они не будут работать, так как мы получаем указатель и начинаем работать с указателем, как с указателем, и общая схема здесь не предусматривает защиты. Второе, можно программно сделать так, чтобы работали флаги, но тогда мы не сможем указывать произвольный адрес, в этом случае система будет подставлять и возвращать в качестве адрес разделенной памяти некоторые свои адреса, обращение к которым будет создавать заведомо ошибочную ситуацию, возникнет прерывание процесса, во время которого система посмотрит - кто и почему был инициатором некорректного обращения к памяти, и если тот процесс имеет нужные права доступа - система подставит нужные адреса, иначе доступ для процесса будет заблокирован. Это похоже на установку контрольной точки в программе при отладке, когда создавалась заведомо ошибочная ситуация для того, чтобы можно было прервать процесс и оценить его состояние.
Третья функция - открепление разделяемой памяти:
int shmdt(char *shmaddr);
shmaddr - адрес прикрепленной к процессу памяти, который был получен при подключении памяти в начале работы.
Четвертая функция - управление разделяемой памятью:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid - идентификатор разделяемой памяти
cmd - команда управления.
В частности, могут быть команды: IPC_SET (сменить права доступа и владельца ресурса - для этого надо иметь идентификатор автора данного ресурса или суперпользователя), IPC_STAT (запросить состояние ресурса - в этом случае заполняется информация в структуру, указатель на которую передается третьим параметром, IPC_RMID (уничтожение ресурса - после того, как автор создал процесс - с ним работают процессы, которые подключаются и отключаются, но не уничтожают ресурс, а с помощью данной команды мы уничтожаем ресурс в системе).
Это все, что касается функций управления разделяемой памятью. )