CodeNet / Языки программирования / C / C++ / Руководства и справочные материалы по C/C++ / Руководство программиста для Linux
Семафоры
6.4.3. Семафоры
Основные понятия
Семафоры лучше всего предствлять себе как счетчики, управляющие доступом к общим ресурсам. Чаще всего они используются как блокирующий механизм, не позволяющий одному процессу захватить ресурс, пока этим ресурсом пользуется другой. Семафоры часто подаются как наиболее трудные для восприятия из всех трех видов IPC-объектов. Для полного понимания, что же такое семафор, мы их немного пообсуждаем, прежде чем переходить к системным вызовам и операционной теории.
Слово "семафор" в действительности является старым железнодорожным термином, соответствующим "рукам", не дающим траекториям каров (это то, что у буржуев ездит по рельсам - перев.) пересекаться на перекрестках. То же самое можно сказать и про семафоры. Семафор в положении ON (руки пондяты вверх) если ресурс свободен и в положении OFF (руки опущены) если ресурс недоступен (должны ждать).
Этот пример неплохо показал суть работы семафора, однако важно знать, что в IPC используются множества семафоров, а не отдельные экземпляры. Разумеется, множество может содержать и один семафор, как в нашем железнодорожном примере.
Возможен другой подход к семафорам - как к счетчикам ресурсов. Приведем другой пример из жизни. Вообразим себе спулер, управляющий несколькими принтерами, каждый из которых обрабатывает по нескольку заданий. Гипотетический менеджер печати будет использовать множество семафоров для установления доступа к каждому из принтеров.
Предположим, что в корпоративной комнате имеются 5 работающих принтеров. Наш менеджер конструирует 5 семафоров - по одному на каждый принтер. Поскольку каждый принтер может обрабатывать только по одному запросу за раз, все семафоры устанавливаются в 1, что означает готовность всех принтеров печатать.
Машенька послала запрос на печать. Менеджер смотрит на семафоры и находит первый из них со значением 1. Перед тем, как Машенькин запрос попадет на физическое устройство, менеджер печати уменьшит соответствующий семафор на 1. Теперь значение семафора есть 0. В мире семафоров System V нуль означает стопроцентную занятость ресурса на семафоре. В нашем примере на принтере не будет ничего печататься, пока значение семафора не изменится.
Когда Машенька напечатала все свои плакаты, менеджер печати увеличивает семафор на 1. Теперь его значение вновь равно 1 и принтер может принимать задания снова.
Не смущайтесь тем, что все семафоры инициализируются единицей. Семафоры, трактуемые как счетчики ресурсов, могут изначально устанавливаться в любое натуральное число, не только в 0 или 1. Если бы наши принтеры умели печатать по 500 документов за раз, мы могли бы проинициализировать семафоры значением 500, уменьшая семафор на 1 при каждом поступающем задании и увеличивая после его завершения. Как вы увидите в следующей главе, семафоры имеют очень близкое отношение к разделяемым участкам памяти, играя роль сторожевой собаки, кусающей нескольких писателей в один и тот же сегмент памяти (имеется в виду машинная память).
Перед тем, как копаться в системных вызовах, коротко пробежимся по внутренним структурам данных, с которыми имеют дело семафоры.
Структура semid_ds ядра
Так же, как и для очередей сообщений, ядро отводит часть своего адресного пространства под структуру данных каждого множества семафоров. Структура определена в linux/sem.h:
/* Структура данных semid для каждого множества семафоров системы */ struct semid_ds { struct ipc_perm sem_perm; /* права доступа, см. ipc.h */ time_t sem_otime; /* время последнего semop-а */ time_t sem_ctime; /* время последнего изменения */ struct sem *sem_base; /* указатель на первый семафор в массиве */ struct wait_queue *eventn; struct wait_queue *eventz; struct sem_undo *undo; /* запросы undo в этом массиве */ ushort sem_nsems; /* номера семафоров в массиве */ };
Так же, как с очередями сообщений, операции с этой структурой проводятся с помощью системных вызовов, а не грязными скальпелями. Вот некоторые описания полей.
sem_perm
Это пример структуры ipc_perm, котораф описана в linux/ipc.h. Она содержит информацию о доступе к множеству семафоров, включая права доступа и информацию о создателе множества (uid и т.д.).
sem_otime
Время последней операции semop() (подробнее чуть позже).
sem_ctime
Время последнего изменения структуры.
sem_base
Указатель на первый семафор в массиве.
sem_undo
Число запросов undo в массиве (подробнее еще чуть позже).
sem_nsems
Количество семафоров в массиве.
Структура sem ядра
В sem_ds есть указатель на базу массива семафоров. Каждый элемент массива имеет тип sem, который описан в linux/sem.h:
/* Структура для каждого семафора в системе */ struct sem { short sempid; /* pid последней операции */ ushort semval; /* текущее значение */ ushort semncnt; /* число процессов, ждущих увеличения semval-а */ ushort semzcnt; /* число процессов, ждущих semval-а , равного 0 */ };
sem_pid
ID процесса, проделавшего последнюю операцию
sem_semval
Текущее значение семафора
sem_semncnt
Число процессов, ожидающих освобождения требуемых ресурсов
sem_semzcnt
Число процессов, ожидающих освобождения всех ресурсов
Системный вызов semget() используется для того, чтобы создать новое множество семафоров или получить доступ к старому.
SYSTEM CALL: semget(); PROTOTYPE: int semget ( key_t key, int nsems, int semflg ); RETURNS: IPC-идентификатор множества семафоров в случае успеха -1 в случае ошибки errno: EACCESS (доступ отклонен) EEXIST (существует нельзя создать (IPC_ESCL)) EIDRM (множество помечено как удаляемое) ENOENT (множество не существует, не было исполнено ни одного IPC_CREAT-а) ENOMEM (не хватает памяти для новых семафоров) ENOSPC (превышен лимит на количество множеств семафоров) NOTES:
Первый аргумент semget() - это ключ (в нашем случае возвращается ftok()-ом). Он сравнивается с ключами остальных множеств семафоров, присутствующих в системе. Вместе с этим решается вопрос о выборе между созданием и подключением к множеству семафоров в зависимости от аргумента msgflg.
IPC_CREAT
Создает множество семафоров, если его еще не было в системе.
IPC_EXCL
При использовании вместе с IPC_CREAT вызывает ошибку, если семафор уже существует.
Если IPC_CREAT используется в одиночку, то semget() возвращает идентификатор множества семафоров - вновь созданного или с таким же ключом. Если IPC_EXCL используется совместно с IPC_CREAT, то либо создается новое множество, либо, если оно уже существует, вызов приводит к ошибке и -1. Сам по себе IPC_EXCL бесполезен, но вместе с IPC_CREAT он дает средство гарантировать, что ни одно из существующих множеств семафоров не открыто для доступа. Как и в других частях System V IPC, восьмеричный режим доступа может быть OR-нут в маску для формирования доступа к множеству семафоров.
Аргумент nems определяет число семафоров, которых требуется породить в новом множестве. Это количество принтеров в нашей корпоративной комнате. Максимальное число семафоров определяется в
"linux/sem.h": #define SEMMSL 32 /* <= 512 */
Аргумент nsems игнорируется, если вы открвываете существующую очередь.
Напишем функции-переходники для открытия и создания множества семафоров.
Обратите внимание на явное задание доступа 0660. Эта незатейливая функция возвращает идентификатор множества семафоров или -1 в случае ошибки. Должны быть также заданы значение ключа и число семафоров для того, чтобы сосчитать память, необходимую для них. В примере, завершающем этот IPC_EXCL используется для определения существует множество семафоров или нет.
Системный вызов semop()
SYSTEMCALL: semop(); PROTOTYPE: int semop( int semid, struct sembuf *sops, unsigned nsops); RETURNS: 0 в случае успеха (все операции выполнены) -1 в случае ошибки errno: E2BIG (nsops больше чем максимальное число позволенных операций) EACCESS (доступ отклонен) EAGAIN (при поднятом флаге IPC_NOWAIT операция не может быть выполнена) EFAULT (sops указывает на ошибочный адрес) EIDRM (множество семафоров уничтожено) EINTR (сигнал получен во время сна) EINVAL (множество не существует или неверный semid) ENOMEM (поднят флаг SEM_UNDO, но не хватает памяти для создания необходимой undo-структуры) ERANGE (значение семафора вышло за пределы допустимых значений) NOTES:
Первый аргумент semop() есть значение ключа (в нашем случае возвращается semget-ом). Второй аргумент (sops) - это указатель на массив операций, выполняемых над семафорами, третий аргумент (nsops) является количеством операций в этом массиве.
Аргумент sops указывает на массив типа sembuf. Эта структура описана в linux/sem.h следующим образом:
/* системный вызов semop требует массив таких структур */ struct sembuf { ushort sem_num; /* индекс семафора в массиве */ short sem_op; /* операция над семафором */ short sem_flg; /* флаги */ };
sem_num
Номер семафора, с которым вы собираетесь иметь дело.
sem_op
Выполняемая операция (положительное, отрицательное число или нуль).
sem_flg
Флаги операции.
Если sem_op отрицателен, то его значение вычитается из семафора (семафор уменьшается - перев.). Это соответствует получению ресурсов, которые контролирует семафор. Если IPC_NOWAIT не установлен, то вызывающий процесс засыпает, пока семафор не выдаст требуемое количество ресурсов (пока другой процесс не освободит их).
Если sem_op положителен, то его значение добавляется к семафору. Это соответствует возвращению ресурсов множеству семафоров приложения. Ресурсы всегда нужно возвращать множеству семафоров, если они больше не используются!
Наконец, если sem_op равен нулю, то вызывающий процесс будет усыплен (sleep()), пока значение семафора не станет нулем. Это соответствует ожиданию того, что ресурсы будут использованы на 100%. Хорошим примером был бы демон, запущенный с суперпользовательскими правами, динамически регулирующий размеры множества семафоров, если оно достигло стопроцентного использования.
Чтобы пояснить вызов semop, вспомним нашу комнату с принтерами. Пусть мы имеем только один принтер, способный выполнять только одно задание за раз. Мы создаем множество семафоров из одного семафора (только один принтер) и устанавливаем его начальное значение в 1 (только одно задание за раз).
Каждый раз, посылая задание на принтер, нам нужно сначала убедиться, что он свободен. Мы делаем это, пытаясь получить от семафора единицу ресурса. Давайте заполним массив sembuf, необходимый для выполнения операции:
struct sembuf sem_lock = { 0, -1, IPC_NOWAIT };
Трансляция вышеописанной инициализации структуры добавит -1 к семафору 0 из множества семафоров. Другими словами, одна единица ресурсов будет получена от конкретного (нулевого) семафора из нашего множества. IPC_NOWAIT установлен, поэтому либо вызов пройдет немедленно, либо будет провален, если принтер занят. Рассмотрим пример инициализации sembuf-а semop-ом:
if(semop(sid, %sem_lock, 1) == -1) perror("semop");
Третий аргумент (nsops) говорит, что мы выполняем только одну (1) операцию (есть только одна структура sembuf в нашем массиве операций). Аргумент sid является IPC идентификатором для нашего множества семафоров.
Когда задание на принтере выполнится, мы должны вернуть ресурсы обратно множеству семафоров, чтобы принтером могли пользоваться другие.
struct sembuf sem_unlock = { 0, 1, IPC_NOWAIT };
Трансляция вышеописанной инициализации структуры добавляет 1 к семафору номер 0 множества семафоров. Другими словами, одна единица ресурсов будет возвращена множеству семафоров.
Системный вызов semctl()
SYSTEM CALL: semctl(); PROTOTYPE: int semctl ( int semid, int semnum, int cmd, union semun arg ); RETURNS: натуральное число в случае успеха -1 в случае ошибки: errno = EACCESS (доступ отклонен) EFAULT (адрес, указанный аргументом arg, ошибочен) EIDRM (множество семафоров удалено) EINVAL (множество не существует или неправильный semid) EPERM (EUID не имеет привилегий для cmd в arg-е) ERANGE (значение семафора вышло за пределы допустимых значений) NOTES: Выполняет операции, управляющие множеством семафоров
Вызов semctl используется для осуществления управления множеством семафоров. Этот вызов аналогичен вызову msgctl для очередей сообщений. Если вы сравните списки аргументов этих двух вызовов, то заметите, что они немного отличаются. Напомним, что семафоры введены скорее как множества, чем как отдельные объекты. С операциями над семафорами требуется посылать не только IPC-ключ, но и конкретный семафор из множества.
Оба системных вызова используют аргумент cmd для определения команды, которая будет выполнена над IPC-объектом. Оставшаяся разница заключается в последнем аргументе. В msgctl он представляет копию внутренней структуры данных ядра. Повторим, что мы используем эту структуру для получения внутренней информации об очереди сообщений либо для установки или изменения прав доступа и владения очередью. Для семафоров поддерживаются дополнительные команды, которые требуют данных более сложного типа в последнем аргументе. Использование объединения (union) огорчает многих новичков до состояния %(. Мы очень внимательно разберем эту структуру, чтобы не возникало никакой путаницы.
Первый аргумент semctl() является ключом (в нашем случае возвращаемым вызовом semget). Второй аргумент (semun) - это номер семафора, над которым совершается операция. По существу, он может быть понят как индекс на множестве семафоров, где первый семафор представлен нулем.
Аргумент cmd представляет собой команду, которая будет выполнена над множеством. Как вы можете заметить, здесь снова присутствуют IPC_STAT/IPC_SET вместе с кучей дополнительных команд, специфичных для множеств семафоров:
IPC_STAT
Берет структуру semid_ds для множества и запоминает ее по адресу аргумента buf в объединении semun.
IPC_SET
Устанавливает значение элемента ipc_perm структуры semid_ds для множества.
IPC_RMID
Удаляет множество из ядра.
GETALL
Используется для получения значений всех семафоров множества. Целые значения запоминаются в массиве элементов unsigned short, на который указывает член объединения array.
GETNCNT
Выдает число процессов, ожидающих ресурсов в данный момент.
GETPID
Возвращает PID процесса, выполнившего последний вызов semop.
GETVAL
Возвращает значение одного семафора из множества.
GETZCNT
Возвращает число процессов, ожидающих стопроцентного освобождения ресурса.
SETALLM
Устанавливает значения семафоров множества, взятые из элемента array объединения.
SETVAL
Устанавливает значение конкретного семафора множества как элемент val объединения.
Аргумент arg вызова semсtl() является примером объединения semun, описанного в linux/sem.h следующим образом:
/* аргумент arg для системного вызова semctl */ union semun { int val; /* значение для SETVAL-а */ struct semid_ds *buf; /* буфер для IPC_STAT и IPC_SET */ ushort *array; /* массив для GETALL и SETALL */ struct seminfo *__buf; /* буфер для IPC_INFO */ void *__pad; };
val
Определяет значение, в которое устанавливается семафор командой SETVAL.
buf
Используется командами IPC_STAT/IPC_SET. Представляет копию внутренней структуры данных семафора, находящейся в ядре.
array
Указатель для команд GETALL/SETALL. Ссылается на массив целых, используемый для установки или получения всех значений семафоров в множестве.
Оставшиеся аргументы __buf и __pad предназначены для ядра и почти, а то и вовсе не нужны разработчику приложения. Эти два аргумента специфичны для LINUX-а, их нет в других системах UNIX-а.
Поскольку этот особенный системный вызов наиболее сложен для восприятия среди всех системных вызовов System V IPC, мы рассмотрим несколько его примеров в действии.
Следующий отрывок выдает значение указанного семафора. Последний аргумент (объединение) игнорируется, если используется команда GETVAL.
int get_sem_val( int sid, int semnum ) { return( semctl(sid, semnum, GETVAL, 0)); }
Возвращаясь к примеру с принтерами, допустим, что потребовалось определить статус всех пяти принтеров:
#define MAX_PRINTERS 5 printer_usage() { int x; for(x=0; xsem_perm.mode); /* Изменяем права доступа к семафору */ sscanf(mode, "%0", &semopts.buf->sem_perm.mode); /* Обновляем внутреннюю структуру данных */ semctl(sid, 0, IPC_SET, semopts); printf("Updated...\n"); }
Программа пытается создать локальную копию внутренней структуры данных для множества семафоров, изменить права доступа и сIPC_SETить их обратно в ядро. Однако, первый вызов semctl-а немедленно вернет EFAULT или ошибочный адрес для последнего аргумента (объединения!). Кроме того, если бы мы не следили за ошибками для этого вызова, то заработали бы сбой памяти. Почему?
Вспомним, что команды IPC_SET/IPC_STAT используют элемент buf объединения, который является указателем на тип semid_ds. Указатели - это указатели, и ничего кроме указателей! Элемент buf должен ссылаться на некий корректный участок памяти, чтобы наша функция работала как полагается. Рассмотрим исправленную версию:
void changemode(int sid, char *mode) { int rc; struct semid_ds mysemds; /* Получаем текущие значения для внутренней структуры данных */ /* Сначала указываем на нашу локальную копию! */ semopts.buf = &mysemds; /* Попробуем еще разок! */ if((rc = semctl(sid, 0, IPC_STAT, semopts)) == -1) { perror("semctl"); exit(1); } printf("Old permissions were %o\n", semopts.buf->sem_perm.mode); /* Изменяем права доступа к семафору */ sscanf(mode, "%0", &semopts.buf->sem_perm.mode); /* Обновляем внутреннюю структуру данных */ semctl(sid, 0, IPC_SET, semopts); printf("Updated...\n"); }
semtool: Интерактивное средство для работы с семафорами
Описание
Поведение semtool()-а зависит от аргументов командной строки, что удобно для вызова из скрипта shell-а. Позволяет делать все, что угодно, от создания и манипулирования до редактирования прав доступа и удаления множества семафоров. Может быть использовано для управления разделяемыми ресурсами через стандартные скрипты shell-а.
Синтаксис командной строки
Создание множества семафоров
semtool c (количество семафоров в множестве)
Запирание семафора
semtool l (номер семафора для запирания)
Отпирание семафора
semtool u (номер семафора для отпирания)
Изменение прав доступа (mode)
semtool m (mode)
Удаление множества семафоров
semtool d
Примеры
semtool c 5
semtool l
semtool u
semtool m 660
semtool d
Исходный текст
/**************************************************************************** Excerpt from "Linux Programmer's Guide - Chapter 6" (C)opyright 1994-1995, Scott Burkett **************************************************************************** MODULE: semtool.c **************************************************************************** Средство командной строки для работы со множествами семафоров в стиле SysV ****************************************************************************/ #include <stdio.h> #include <ctype.h> #include <stdlib.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>#define SEM_RESOURCE_MAX 1 /* Начальное значение для всех семафоров */ void opensem(int *sid, key_t key); void createsem(int *sid, key_t key, int members); void locksem(int sid, int member); void unlocksem(int sid, int member); void removesem(int sid); unsigned short get_member_count(int sid); int getval(int sid, int member); void dispval(int sid, int member); void changemode(int sid, char *mode); void usage(void); int main(int argc, char *argv[]) { key_t key; int semset_id; if(argc ==1) usage(); /* Создаем особый ключ через вызов ftok() */ key = ftok(".", 's'); switch(tolower(argv[1][0])) { case 'c': if(argc != 3) usage(); createsem(&semset_id, key, atoi(argv[2])); break; case 'l': if(argc != 3) usage(); opensem(&semset_id, key); locksem(semset_id, atoi(argv[2])); break; case 'u': if(argc != 3) usage(); opensem(&semset_id, key); unlocksem(semset_id, atoi(argv[2])); break; case 'd': opensem(&semset_id, key); removesem(semset_id); break; case 'm': opensem(&semset_id, key); changemode(semset_id, argv[2]); break; default: usage(); } return(0); } void opensem(int *sid, key_t key) { /* Открываем множество семафоров - не создаем! */ if((*sid = semget(key, 0, 0666)) == -1) { printf("Semaphore set does not exist!\n"); exit(1); } } void createsem(int *sid, key_t key, int members) { int cntr; union semun semopts; if(members > SEMMSL) { printf("Sorry, max number of semaphores in a set is %d\n", SEMMSL); exit(1); } printf("Attempting to create new semaphore set with %d members\n", members); if((*sid = semget(key, members, IPC_CREAT|IPC_EXCL|0666)) == -1) { fprintf(stderr, "Semaphore set already exists!\n"); exit(1); } semopts.val = SEM_RESOURCE_MAX; /* Инициализируем все элементы (может быть сделано с SETALL) */ for(cntr=0; cntr(get_member_count(sid)-1)) { fprintf(stderr, "semaphore member %d out of range\n", member); return; } /* Попытка запереть множество семафоров */ if(!getval(sid, member)) { fprintf(stderr, "Semaphore resources exhausted (no lock)!\n"); exit(1); } sem_lock.sem_num = member; if((semop(sid, &sem_lock, 1)) == -1) { fprintf(stderr, "Lock failed\n"); exit(1); } else printf("Semaphore resources decremented by one (locked)\n"); dispval(sid, member); } void unlocksem(int sid, int member) { struct sembuf sem_unlock={ member, 1, IPC_NOWAIT }; int semval; if( member<0 || member>(get_member_count(sid)-1)) { fprintf(stderr, "semaphore member %d out of range\n", member); return; } /* Заперто ли множество семафоров? */ semval = getval(sid, member); if(semval == SEM_RESOURCE_MAX) { fprintf(stderr, "Semaphore not locked!\n"); exit(1); } sem_unlock.sem_num = member; /* Попытка запереть множество семафоров */ if((semop(sid, &sem_unlock, 1)) == -1) { fprintf(stderr, "Unlock failed\n"); exit(1); } else printf("Semaphore resources incremented by one (unlocked)\n"); dispval(sid, member); } void removesem(int sid) { semctl(sid, 0, IPC_RMID, 0); printf("Semaphore removed\n"); } unsigned short get_member_count(int sid) { union semun semopts; struct semid_ds mysemds; semopts.buf = &mysemds; /* Выдает количество элементов во множестве семафоров */ return(semopts.buf->sem_nsems); } int getval(int sid, int member) { int semval; semval = semctl(sid, member, GETVAL, 0); return(semval); } void changemode(int sid, char *mode) { int rc; union semun semopts; struct semid_ds mysemds; /* Получаем текущее значение для внутренней структуры данных */ semopts.buf = &mysemds; rc = semctl(sid, 0, IPC_STAT, semopts); if (rc == -1) { perror("semctl"); exit(1); } printf("Old permissions were %o\n", semopts.buf->sem_perm.mode); /* Изменяем права доступа к семафору */ sscanf(mode, "%ho", &semopts.buf->sem_perm.mode); /* Обновляем внутреннюю структуру данных */ semctl(sid, 0, IPC_SET, semopts); printf("Updated...\n"); } void dispval(int sid, int member) { int semval: semval = semctl(sid, member, GETVAL, 0); printf("semval for member %d is %d\n", member, semval); } void usage(void) { fprintf(stderr, "semtool - A utility for tinkering with semaphores\n"); fprintf(stderr, "\nUSAGE: semtool4 (c)reate \n"); fprintf(stderr, " (l)ock \n"); fprintf(stderr, " (u)nlock \n"); fprintf(stderr, " (d)elete\n"); fprintf(stderr, " (m)ode \n"); exit(1); } semstat: Программа-компаньон для semtool В дополнение к semtool, приведем исходный текст программы-компаньона semstat. Она выводит на экран значение каждого из семафоров множества, созданного посредством semtool. /**************************************************************************** Excerpt from "Linux Programmer's Guide - Chapter 6" (C)opyright 1994-1995, Scott Burkett **************************************************************************** MODULE: semstat.c **************************************************************************** Компаньон для cредства командной строки semtool-а. Semstat выводит на экран текущее значение всех семафоров множества, созданного посредством semtool. ****************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int get_sem_count(int sid); void show_sem_usage(int sid); int get_sem_count(int sid); void dispval(int sid); int main(int argc, char *argv[]) { key_t key; int semset_id; /* Создаем уникальный ключ через вызов ftok() */ key = ftok(".", 's'); /* Открываем множество семафоров - не создаем! */ if((semset_id = semget(key, 1, 0666)) == -1) { printf("Semaphore set does not exist!\n"); exit(1); } show_sem_usage(semset_id); return(0); } void show_sem_usage(int sid) { int cntr=0, maxsems, semval; maxsems = get_sem_count(sid); while(cntr < maxsems) { semval = semctl(sid, cntr, GETVAL, 0); printf("Semaphore #%d: --> %d\n", cntr, semval); cntr++; } } int get_sem_count(int sid) { int rc; struct semid_ds mysemds; union semun semopts; /* Получаем текущие значения для внутренней структуры данных */ semopts.buf = &mysemds; if((rc = semctl(sid, 0, IPCJ_STAT, semopts)) == -1) { perror("semctl"); exit(1); } /* Выдаем количество семафоров в множестве */ return(semopts.buf->sem_nsems); } void dispval(int sid) { int semval; semval = semctl(sid, 0, GETVAL, 0); printf("semval is %d\n", semval); }
6.4.4. Разделяемая память
Основные понятия
Разделяемая память может быть наилучшим образом описана как отображение участка (сегмента) памяти, которая будет разделена между более чем одним процессом. Это гораздо более быстрая форма IPC, потому что здесь нет никакого посредничества (т.е. каналов, очередей сообщений и т.п.). Вместо этого, информация отображается непосредственно из сегмента памяти в адресное пространство вызывающего процесса. Сегмент может быть создан одним процессом и впоследствии использован для чтения/записи любым количеством процессов.
Внутренние и пользовательские структуры данных
Давайте взглянем на структуру данных, поддерживаемую ядром, для разделяемых сегментов памяти.
Структура ядра shmid_ds
Так же, как для очередей сообщений и множеств семафоров, ядро поддерживает специальную внутреннюю структуру данных для каждого разделяемого сегмента памяти, который существует врутри его адресного пространства. Такая структура имеет тип shmid_ds и определена в Linux/shm.h как следующая:
/* Структура данных shmid для каждого разделяемого сегмента памяти системы */ 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; /* pid создателя */ unsigned short shm_lpid; /* pid последнего пользователя сегмента */ short shm_nattch; /* номер текущей привязки */ /* следующее носит частный характер */ unsigned short shm_npages; /* размеры сегмента (в страницах) */ unsigned long *shm_pages; /* массив указателей на $frames -> S$ */ struct vm_area_struct *attaches; /* дескрипторы для привязок */ };
Операции этой структуры исполняются посредством специального системного вызова и с ними не следует работать непосредственно. Вот описания полей, наиболее относящихся к делу:
shm_perm
Это образец структуры ipc_perm, который определен в Linux/ipc.h. Он содержит информацию о доступе к сегменту, включая права доступа и информацию о создателе сегмента (uid и т.п.).
shm_segsz
Размеры сегмента (в байтах).
shm_atime
Время последней привязки к сегменту.
shm_dtime
Время последней отвязки процесса от сегмента.
shm_ctime
Время последнего изменения этой структуры (изменение mode и т.п.).
shm_cpid
PID создавшего процесса.
shm_lpid
PID последнего процесса обратившегося к сегменту.
shm_nattch
Число процессов, привязанных к сегменту на данный момент.
Системный вызов shmget()
Чтобы создать новый разделяемый сегмент памяти или получить доступ к уже существующему, используется системный вызов shmget().
SYSTEM CALL: shmget(); PROTOTYPE: int shmget ( key_t key, int size, int shmflg ); RETURNS: идентификатор разделяемого сегмента памяти в случае успеха -1 в случае ошибки: errno = EINVAL (Ошибочно заданы размеры сегмента) EEXIST (Сегмент существует, нельзя создать) EIDRM (Сегмент отмечен для удаления, или был удален) ENOENT (Сегмент не существует) EACCESS (Доступ отклонен) ENOMEM (Недостаточно памяти для создания сегмента) NOTES:
Этот новый вызов должен выглядеть для вас почти как старые новости. Он поразительно похож на соответствующие вызовы get для очередей сообщений и множеств семафоров.
Первый аргумент для shmget() - это значение ключа (в нашем случае возвращен посредством вызова ftok()-а). Это значение ключа затем сравнивается с существующими значениями, которые находятся внутри ядра для других разделяемых сегментов памяти. В этом отношении операция открытия или получения доступа зависит от содержания аргумента shmflg.
IPC_CREAT
Создает сегмент, если он еще не существует в ядре.
IPC_EXCL
При использовании совместно с IPC_CREAT приводит к ошибке, если сегмент уже существует.
Если используется один IPC_CREAT, то shmget() возвращает либо идентификатор для вновь созданного сегмента, либо идентификатор для сегмента, который уже существует с тем же значением ключа. Если вместе с IPC_CREAT используется IPC_EXCL, тогда либо создается новый сегмент, либо, если сегмент уже существует, вызов "проваливается" с -1. IPC_EXCL сам по себе бесполезен, но если он комбинируется с IPC_CREAT, то может быть использован как способ получения гарантии, что нет уже существующих сегментов, открытых для доступа.
Повторимся, (необязательный) восьмеричный доступ может быть объеденен по ИЛИ в маску доступа.
Давайте создадим функцию-переходник для обнаружения или создания разделяемого сегмента памяти:
int open_segment( key_t keyval, int segsize ) { int shmid; if((shmid = shmget( keyval, segsize, IPC_CREAT | 0660 )) == -1) { return(-1); } return(shmid); }
Отметьте явное использование 0660 для прав доступа. Эта небольшая функция выдает либо идентификатор разделяемого сегмента памяти (int), либо -1 в случае ошибки. Значение ключа и заказанные размеры сегмента (в байтах) передаются в виде аргументов.
Как только процесс получает действующий идентификатор IPC для выделяемого сегмента, следующим шагом является привязка или размещение сегмента в адресном пространстве процесса.
Системный вызов shmat()
SYSTEM CALL: shmat(); PROTOTYPE: int shmat ( int shmid, char *shmaddr, int shmflg ); RETURNS: адрес, по которому сегмент был привязан к процессу, в случае успеха -1 в случае ошибки: errno = EINVAL (Ошибочно значение IPC ID или адрес привязки) ENOMEM (Недостаточно памяти для привязки сегмента) EACCES (Права отклонены) NOTES:
Если аргумент addr является нулем, ядро пытается найти нераспределенную область. Это рекомендуемый метод. Адрес может быть указан, но типично это используется только для облегчения работы аппаратного обеспечения или для разрешения конфликтов с другими приложениями. Флаг SHM_RND может быть OR-нут в аргумент флага, чтобы заставить переданный адрес выровняться по странице (округление до ближайшей страницы).
Кроме того, если устанавливается флаг SHM_RDONLY, то разделяемый сегмент памяти будет распределен, но помечен readonly.
Этот вызов, пожалуй, наиболее прост в использовании. Рассмотрим функцию-переходник, которая по корректному идентификатору сегмента возвращает адрес привязки сегмента:
char *attach_segment( int shmid ) { return(shmat(shmid, 0, 0)); }
Если сегмент был правильно привязан, и процесс имеет указатель на начало сегмента, чтение и запись в сегмент становятся настолько же легкими,как манипуляции с указателями. Не потеряйте полученное значение указателя! Иначе у вас не будет способа найти базу (начало) сегмента.
Системный вызов shmctl() SYSTEM SALL: shmctl(); PROTOTYPE: int shmctl ( int shmqid, int cmd, struct shmid_ds *buf ); RETURNS: 0 в случае успеха -1 в случае ошибки: errno = EACCESS (Нет прав на чтение при cmd, равном IPC_STAT) EFAULT (Адрес, на который указывает буфер, ошибочен при cmd, равном IPC_SET или IPC_STAT) EIDRM (Сегмент был удален во время вызова) EINVAL (ошибочный shmqid) EPERM (попытка выполнить команду IPC_SET или IPC_RMID но вызывающий процесс не имеет прав на запись (измените права доступа)) NOTES:
Вызов очень похож на msgctl(), выполняющий подобные задачи для очередей сообщений. Поэтому мы не будем слишком детально его обсуждать. Употребляемые значения команд следующие:
IPC_STAT
Берет структуру shmid_ds для сегмента и сохрает ее по адресу, указанному buf-ом.
IPC_SET
Устанавливает значение ipc_perm-элемента структуры shmid_ds. Сами величины берет из аргумента buf.
IPC_RMID
Помечает сегмент для удаления.
IPC_RMID в действительности не удаляет сегмент из ядра, а только помечает для удаления. Настоящее же удаление не происходит, пока последний процесс, привязанный к сегменту, не "отвяжется" от него как следует. Конечно, если ни один процесс не привязан к сегменту на данный момент, удаление осуществляется немедленно.
Снятие привязки производит системный вызов shmdt.
Системный вызов shmdt() SYSTEM SALL: shmdt(); PROTOTYPE: int shmdt ( char *shmaddr ); RETURNS: -1 в случае ошибки: errno = EINVAL (ошибочно указан адрес привязки)
После того, как разделяемый сегмент памяти больше не нужен процессу, он должен быть отсоединен вызовом shmdt(). Как уже отмечалось, это не то же самое, что удаление сегмента из ядра. После успешного отсоединения значение элемента shm_nattch структуры shmid_ds уменьшается на 1. Когда оно достигает 0, ядро физически удаляет сегмент.
shmtool: Интерактивный обработчик разделяемых сегментов памяти
Описание
Наш последний пример объектов System V IPC - это shmtool: средство командной строки для создания, чтения, записи и удаления разделяемых сегментов памяти. Так же, как и в предыдущий примерах, во время исполнения любой операции сегмент создается, если его прежде не было.
Синтаксис командной строки
Запись строк в сегмент
shmtool w "text"
Получение строк из сегмента
shmtool r
Изменение прав доступа (mode)
shmtool m (mode)
Удаление сегмента
shmtool d
Примеры
shmtool w test
shmtool w "This is a test"
shmtool r
shmtool d
shmtool m 660
Код
#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #define SEGSIZE 100main(int argc, char *argv[]) { key_t key; int shmid, cntr; char *segptr; if(argc == 1) usage(); /* Создаем особый ключ через вызов ftok() */ key = ftok(".", 'S'); /* Открываем разделяемый сегмкнт памяти - если нужно, создаем */ if((shmid = shmget(key, SEGSIZE, IPC_CREAT|IPC_EXCL|0660)) == -1) { printf("Shared memory segment exists - opening as a client\n"); /* Скорее всего, сегмент уже существует - попробуем как клиент */ if((shmid = shmget(key, SEGSIZE, 0)) == -1) { perror("shmget"); exit(1); } } else { printf("Creating new shared memory segment\n"); } /* Привязывем (распределяем) разделяемый сегмент памяти в текущем процессе */ if((segptr = shmat(shmid, 0, 0)) == -1) { perror("shmat"); exit(1); } switch(tolower(argv[1][0])) { case 'w': writeshm(shmid, segptr, argv[2]); break; case 'r': readshm(shmid, segptr); break; case 'd': removeshm(shmid); break; case 'm': changemode(shmid, argv[2]); break; default: usage(); } } writeshm(int shmid, char *segptr, char *text) { strcpy(segptr, text); printf("Done...\n"); } readshm(int shmid, char *segptr) { printf("segptr: %\n", segptr); } removeshm(int shmid) { shmctl(shmid, IPC_RMID, 0); printf("Shared memory segment marked for deletion\n"); } changemode(int shmid, char *mode) { struct shmid_ds myshmds; /* Копируем текущее значение для внутренней структуры данных в myshmds */ shmctl(shmid, IPC_STAT, &myshmds); /* Выводим на экран старые права доступа */ printf("Old permissions were: %o\n", myshmds.shm_perm.mode); /* Исправляем значение режима доступа в копии */ sscanf(mode, "%o", &myshmds.shm_perm.mode); /* Обновляем действующее значение режима доступа */ shmctl(shmid, IPC_SET, &myshmds); printf("New permissions are: %o\n", myshmds.shm_perm.mode); } usage() { fprintf(stderr, "shmtool - A utility for tinkering with shared memory\n"); fprintf(stderr, "\nUSAGE: shmtool (w)rite \n"); fprintf(stderr, " (r)ead\n"); fprintf(stderr, " (d)elete\n"); exit(1); fprintf(stderr, " (m)ode change \n"); exit(1); }