171
Общий список системных вызовов, обеспечивающих работу с разделяемыми сегментами памяти, имеет вид:
#include <sys/ipc.h>
#include <sys/shm.h>
int |
shmget(key_t key, |
size_t |
size, |
int shmflg); |
|
void |
*shmat(int |
shmid, |
const void *shmaddr, int shmflg); |
||
int |
shmdt (const void * |
|
shmaddr); |
|
|
int |
shmctl(int |
shmid, |
int |
cmd, struct shmid_ds *buf); |
|
Рассмотрим каждую из указанных функций по отдельности.
Создание нового или получение идентификатора уже существующего разделяемого сегмента памяти осуществляется системным вызовом shmget(...), имеющего вид:
int shmget(key_t key, size_t size, int shmflg);
где key — ключ доступа к средствам пакета IPC, полученный с помощью функции ftok(...), или - специальное значение IPC_PRIVATE; причем, в последнем случае, создается приватный разделяемый сегмент памяти (см. замечание ниже);
size — задает требуемый размер сегмента в байтах, который выравнивается до размера, кратного PAGE_SIZE;
shmflg – флаги, которые играют роль только при создании нового сегмента разделяемой памяти и определяют права различных пользователей при доступе к этому сегменту; сами флаги являются побитной комбинацией значений, выполняемых с помощью побитной операции " или " – " | ", что задает права доступа:
•IPC_CREAT – если сегмента для указанного ключа не существует, он должен быть создан;
•IPC_EXCL – применяется совместно с флагом IPC_CREAT. При совместном
их использовании и существовании сегмента с указанным ключом, доступ к сегменту не производится и констатируется ошибочная ситуация, при этом переменная errno, описанная в файле <errno.h>, примет значение EEXIST;
•0400 – разрешено чтение для пользователя, создавшего сегмент;
•0200 – разрешена запись для пользователя, создавшего сегмент;
•0040 – разрешено чтение для группы пользователя, создавшего сегмент;
•0020 – разрешена запись для группы пользователя, создавшего сегмент;
•0004 – разрешено чтение для всех остальных пользователей;
•0002 – разрешена запись для всех остальных пользователей.
Замечание
Существует два варианта создания ключа key для вызова shmget(...):
•Стандартный способ. В качестве значения ключа для системного вызова
используется значение, сформированное функцией ftok(...) для некоторого имени файла и номера экземпляра области разделяемой памяти. В качестве флагов поставляется комбинация прав доступа к создаваемому сегменту и флага
172
IPC_CREAT. Если сегмент для данного ключа еще не существует, то система будет пытаться создать его с указанными правами доступа. Если же вдруг он уже существовал, то мы просто получим его дескриптор. Возможно добавление к этой комбинации флагов флага IPC_EXCL, гарантирующий нормальное завершение системного вызова только в том случае, если сегмент действительно был создан (т. е. ранее он не существовал), если же сегмент существовал, то системный вызов завершится с ошибкой, и значение системной переменной errno, описанной
в файле errno.h, будет установлено в EEXIST.
•Нестандартный способ. В качестве значения ключа указывается специальное значение IPC_PRIVATE. Использование значения IPC_PRIVATE всегда приво-дит к попытке создания нового сегмента разделяемой памяти с заданными правами доступа и с ключом, который не совпадает со значением ключа ни одного из уже существующих сегментов и который не может быть получен с помощью функции ftok(...) ни при одной комбинации ее параметров. Наличие флагов IPC_CREAT и IPC_EXCL, в этом случае, игнорируется.
При удачном завершении вызова, shmget(...) возвращается идентификатор сегмента shmid, и -1 при ошибке, причем переменная errno приобретает одно из следующих значений:
•EINVAL - если создается новый сегмент, а size < SHMMIN или size >
SHMMAX, либо новый сегмент не был создан; второй вариант - сегмент с данным ключем существует, но size больше чем размер этого сегмента;
•EEXIST - если значение IPC_CREAT | IPC_EXCL было указано, а сегмент уже существует;
•ENOSPC - если все возможные идентификаторы сегментов уже распределены
(SHMMNI) или если размер выделяемого сегмента превысит системные лимиты (SHMALL);
•ENOENT - если не существует сегмента для ключа key, а значение IPC_CREAT не указано;
•EACCES - если у пользователя нет прав доступа к сегменту разделяемой памяти;
•ENOMEM - если в памяти нет свободного для сегмента пространства.
Замечание
Ядро ОС, для каждого созданного разделяемого сегмента памяти, создает управляющий набор данных, который определяется структурами:
struct shmid_ds { |
|
|
struct ipc_perm |
shm_perm; |
// права операции |
int |
shm_segsz; |
// размер сегмента (в байтах) |
time_t |
shm_atime; |
// время последнего подключения |
time_t |
shm_dtime; |
// время последнего отключения |
time_t |
shm_ctime; |
// время последнего изменения |
unsigned short |
shm_cpid; |
// идентификатор процесса создателя |
unsigned short |
shm_lpid; |
// идентификатор последнего пользователя |
short |
shm_nattch; |
// количество подключений |
}; |
|
|
struct ipc_perm { |
|
|
key_t key; |
|
|
ushort uid; // действующие идентификаторы владельца и группы euid и egid
173
ushort gid;
ushort cuid; // действующие идентификаторы создателя euid и egid ushort cgid;
ushort mode; // младшие 9 битов shmflg ushort seq; // номер последовательности
};
В дальнейшем, программист может использовать эту информацию, например, с помощью системного вызова shmctl(...).
Уже созданный сегмент разделяемой памяти подстыковывается к адресному пространству процесса с помощью системного вызова:
void *shmat(int shmid, const void *shmaddr, int shmflg);
где shmid - задает идентификатор разделяемого сегмента, полученный с помощью функции shmget(...);
shmaddr - адрес, по которому сегмент должен быть присоединен, причем, если:
•shmaddr == NULL, то система выбирает для подстыкованного сегмента подходящий (неиспользованный) адрес;
•shmaddr != NULL, то подстыковка производится по адресу shmaddr;
shmflg — флаг, определяющий свойства разделяемого сегмента памяти:
•по умолчанию, значение=0 и присоединяемый сегмент будет доступен и на чтение, и на запись, если процесс обладает необходимыми правами;
•флаг SHM_RDONLY предписывает присоединить сегмент только для чтения.
Замечание
Поскольку свойства сегментов зависят от аппаратных особенностей управления памятью, не всякий адрес является приемлемым:
•если установлен флаг SHM_RND, адрес присоединения округляется до величины, кратной константе SHMLBA;
•если shmaddr задан как пустой указатель (NULL), то система выбирает адрес присоединения по своему усмотрению.
Другим важным вопросом является поведение сегментов разделяемой памяти при выполнении процессом системных вызовов fork(), exec() и функции exit():
•при выполнении системного вызова fork(...), все области разделяемой памяти,
размещенные в адресном пространстве процесса, наследуются порожден-ным процессом;
•при выполнении системных вызовов exec(...) и функции exit(...), все области
разделяемой памяти, размещенные в адресном пространстве процесса, исключаются из его адресного пространства, но продолжают существовать в операционной системе.
При удачном выполнении, системный вызов shmat(...) обновляет содержимое структуры shmid_ds, связанной с разделяемым сегментом памяти, следующим образом:
174
•shm_atime устанавливается в текущее время;
•shm_lpid устанавливается в идентификатор вызывающего процесса;
•shm_nattch увеличивается на 1.
Вслучае ошибки, функция shmat(...) возвращает значение -1, а переменной errno присваивается номер ошибки:
•EACCES - вызывающий процесс не имеет прав для подстыковки заданного типа;
•EINVAL - неправильное значение shmid, не выравненное по границе страницы
или неправильное значение shmaddr, или ошибка подключения к brk, или SHM_REMAP было определено, но shmaddr равно NULL;
•ENOMEM - не хватает памяти для описателя или таблиц страниц.
Чтобы закрепить изученный материал, рассмотрим взаимодействие двух программ, каждая из которых фиксирует:
•собственное число запусков;
•а также общее число всех запусков.
Для решения подобной задачи, достаточно использовать разделяемый массив
памяти, состоящий из трех целых чисел:
•первый элемент массива используется как счетчик запуска программы 1;
•второй элемент – счетчик запуска программы 2;
•третий элемент – счетчик запуска обеих программ суммарно.
Замечание
Программа, которая запущенна первой, должна проинициализировать разделяемую память.
На листинге 5.1, приведен исходный текст программы 1, которая изменяет только первый и третий элементы массива целых чисел в разделяемом сегменте памяти.
Листринг 5.1 - Текст программы 1, использующей разделяемый сегмент памяти
/* Программа 1 (lab11.1.с) для иллюстрации работы с разделяемой памятью.
*Мы организуем разделяемую память для массива из трех целых чисел.
*Первый элемент массива является счетчиком числа запусков программы 1,
*т.е. данной программы, второй элемент массива – счетчиком числа запусков
*программы 2, третий элемент массива – счетчиком числа запусков обеих программ */
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> #include <stdlib.h> #include <errno.h>
int main() |
|
{ |
|
int *array; |
// Указатель на разделяемую память |
int shmid; |
// IPC дескриптор для области разделяемой памяти |
int new = 1; |
// Флаг необходимости инициализации элементов массива |
175
//Имя файла, используемое для генерации ключа.
//Файл с таким именем должен существовать в текущей директории char pathname[] = "/home/upk/lab11";
key_t key; |
// IPC ключ |
//Генерируем IPC ключ из имени файла и
//номера экземпляра области разделяемой памяти 0 if((key = ftok(pathname,1)) < 0){
perror("lab11.1 - Не могу сгенерировать ключ..."); exit(-1);
}
//Пытаемся эксклюзивно создать разделяемую память для сгенерированного
//ключа, т.е. если для этого ключа она уже существует, системный вызов
//вернет отрицательное значение.
//Размер памяти определяем как размер массива из трех целых переменных,
//права доступа 0666 – чтение и запись разрешены для всех
if((shmid = shmget(key, 3*sizeof(int), 0666|IPC_CREAT|IPC_EXCL)) < 0){
//В случае ошибки пытаемся определить: возникла ли она из-за того,
//что сегмент разделяемой памяти уже существует или по другой причине if(errno != EEXIST){
// Если по другой причине – прекращаем работу perror("lab11.1 - Не могу открыть разделяемую память"); exit(-1);
}else {
//Если из-за того, что разделяемая память уже существует,
//то пытаемся получить ее IPC дескриптор и, в случае удачи,
//сбрасываем флаг необходимости инициализации элементов массива
if((shmid = shmget(key, 3*sizeof(int), 0)) < 0){ perror("lab11.1 - Не могу найти разделяемую память: "); exit(-1);
}
new = 0;
}
}
//Пытаемся отобразить разделяемую память в адресное пространство текущего
//процесса. Обратите внимание на то, что для правильного сравнения мы явно
//преобразовываем значение -1 к указателю на целое.
if((array = (int *)shmat(shmid, NULL, 0)) == (int *)(-1)){ perror("lab11.1 - Не могу подсоединить разделяемую память: "); exit(-1);
}
//В зависимости от значения флага new либо инициализируем массив,
//либо увеличиваем соответствующие счетчики
if(new){
array[0] = 1; array[1] = 0; array[2] = 1;
} else {
array[0] += 1; array[2] += 1;
}
//Печатаем новые значения счетчиков, удаляем
//разделяемую память из адресного пространства
//текущего процесса и завершаем работу
printf("lab11.1: Программа 1 была запущена %d раз, программа 2 - %d раз, Общее - %d раз\n", array[0], array[1], array[2]);
if(shmdt(array) < 0){
perror("lab11.1: Не могу отсоединить разделяемую память: "); exit(-1);
}
return 0;
}