Материал: 6262

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

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;

}

Источник: https://studfile.net/preview/16438530/