unsigned short shm_npages;
/* размеры сегмента (в страницах) */
/* массив указателей на $frames -> S$*/
unsigned long *shm_pages;
struct vm_area_struct *attaches;
/* дескрипторы для привязок */
};
Я немного сократил эту структуру, оставив описание только нужных нам полей. Полагаю, что вы в нем разберетесь. Возможно, вас заинтересовали термины «привязка» и «отвязка». Привязка — это размещение сегмента в адресном пространстве процесса, подключение к разделяемому сегменту памяти (РСП). Отвязка, соответственно, — отключение, Поле shm_nattch содержит количество привязок к РСП на данный момент.
Для создания нового РСП используется системный вызов shmget(). Этот же вызов используется для подключения к уже существующему РСП.
int shmget(key_t key, int size, int shmflg);
Первый аргумент — это ключ IPC, полученный с помощью ftok(), второй — размер РСП в байтах, а третий — флаги системного вызова shmget. Если установлен флаг IPC_CREAT, системный вызов создаст новый РСП или подключится к уже существующему сегменту, если обнаружится, что уже есть такой сегмент (с таким же значением ключа). Если установлен флаг IPC_EXCL вместе с IPC_CREAT (сам по себе он бесполезен) подключение к существующему РСП запрещается.
Системный вызов shmget() возвращает идентификатор РСП или -1, если произошла ошибка. Переменная errno устанавливается так:
♦ EACCESS — не хватает полномочий для доступа к сегменту;
♦ EINVAL — неправильно заданы размеры сегмента;
♦ EEXISTS — сегмент уже существует, создание невозможно. Вы получите эту ошибку, если будете использовать флаг IPC_EXCL вместе с IPC_CREAT при условии, что сегмент уже существует;
♦ IDRM — сегмент помечен на удаление или уже удален;
♦ ENOMEM — не хватает памяти для создания сегмента.
Приведем пример функции открытия/создания РСП:
int open_shms(key_t key, int size) {
return (shmget(key, size, IPC_CREAT | 0660 )) == -1));
}
После получения идентификатора РСП мы должны «привязаться» к этому сегменту, то есть разместить сегмент в своем адресном пространстве. Для этого используется системный вызов shmat() (shared memory attachment):
int shmat(int shmid, char *shmaddr, int shmflg);
Первый аргумент — это идентификатор РСП, который мы получаем с помощью предыдущего вызова. Второй аргумент — это адрес привязки. Если указать вместо адреса ноль, то ядро само найдет нераспределенную область.
Третий аргумент — это флаги. Обычно используется два флага:
♦ SHM_RND — переданный адрес будет округлен до ближайшей страницы (если вы сами указываете адрес);
♦ SHM_RDONLY — РСП будет доступен только для чтения.
В случае успеха shmat() возвращает адрес, по которому сегмент был привязан к процессу, или -1, если произошла ошибка. Переменная errno может принимать всего три значения:
♦ EACCESS — нет доступа;
♦ ENOMEM — не хватает памяти;
♦ EINVAL — ошибка в параметрах, то есть неправильное значение ID или адреса привязки (shmaddr).
Пример привязки:
char *ptr;
prt = shmat(sh_id,0,0);
После привязки сегмента к адресному пространству доступны операции чтения и записи, которые очень напоминают работу с простыми указателями.
Для снятия привязки используется системный вызов shmdt():
int shmdt(char *shmaddr);
В случае ошибки данный системный вызов возвращает -1. Значение errno только одно: EINVAL, то есть вы неправильно указали адрес привязки. После отвязки значение элемента shm_nattch структуры shmid_ds уменьшается на 1. Если больше нет привязок, то есть shm_nattch = 0, сегмент будет удален ядром.
Для управления РСП используется системный вызов shmctl():
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
Первый аргумент — это идентификатор РСП, второй — команда, а третий — буфер для команд IPC_STAT/IPC_SET. Команд для управления три:
♦ IPC_STAT — сохраняет структуру shmid_ds по адресу buf;
♦ IPC_SET — берет значение элемента ipc_perm структуры shmid_ds и устанавливает его для сегмента. Значение берется из buf;
♦ IPC_RMID — помечает сегмент для удаления, само удаление произойдет, как только последний процесс отвяжется от сегмента. Если сегмент помечен на удаление, ни один процесс не сможет привязаться к сегменту.
В случае успеха системный вызов shmctl() возвращает 0 или -1, если произошла ошибка. Переменная errno устанавливается так:
♦ EACCESS — нет прав;
♦ EFAULT — ошибочный адрес buf;
♦ EIDRM — сегмент помечен на удаление;
♦ EINVAL — неправильный идентификатор сегмента.
Вот теперь мы готовы к написанию демонстрационной программы для работы с разделяемыми сегментами памяти.
Листинг 26.6. Демонстрационная программа shm_demo.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
/* размер нашего сегмента - 256 байтов */
#define SIZE 256
int main(int argc, char *argv[]) {
key_t key; /* ключ */
int shmid, с; /* идентификатор */
char *ptr; /* указатель, через который мы будем
работать с сегментом */
/* Если аргументы не указаны ... */
if (argc == 1) {
printf("shm_demo usage:n");
printf("shm_demo -w string записать строку в сегментn");
printf("shm_demo -r прочитать инф. из сегментаn");
printf("shm_demo -d удалить сегментn");
printf("shm_demo -m mode изменить права доступаn");
exit(1);
}
/* Генерируем ключ IPC */
key = ftok(".", 'D');
/* Пытаемся создать сегмент */
if ((shmid =
shmget(key, SIZE, IPC_CREAT|IPC_EXCL|0660)) == -1) {
printf("Сегмент существует, подключаемся к нему...n");
/* Используем shmget без IPC_EXCL */
if ((shmid = shmget(key, SIZE, 0)) == -1) {
printf("Ошибка в shmgetn");
exit(1);
}
} else {
printf("Создаем новый сегментn");
}
/* Привязываемся к сегменту */
if ((ptr = shmat(shmid, 0, 0)) == -1) {
perror("shmat");
exit(1);
}
/* Разбираем параметры командной строки:
w - запись в сегмент
r - чтение
d - удаление сегмента
m — изменение прав доступа */
switch(tolower(argv[1][1])) {
case 'w':
shm_write(shmid, ptr, argv[2]);
break;
case 'r':
shm_read(shmid, ptr);
break;
case 'd':
shm_rm(shmid);
break;
case 'm':
shm_change_mode(shmid, argv[2]);
break;
}
}
/* Функция для записи в сегмент: ей нужно передать
ID сегмента, адрес привязки и записываемую информацию */
shm_write(int shmid, char *ptr, char *info) {
strcpy(ptr, info);
}
/* Функция чтения информации из сегмента */
shm_read(int shmid, char *ptr) {
printf("Информация из сегмента: %sn", ptr);
}
/* Функция удаления сегмента */
shm_rm(int shmid) {
shmctl(shmid, IPC_RMID, 0);
printf("Сегмент помечен на удалениеn");
}
/* Функция изменения прав доступа. Ей нужно передать
идентификатор сегмента и права доступа в виде строки,
например, "0666" * /
shm_change_mode(int shmid, char *mode) {
struct shmid_ds mds;
shmctl(shmid, IPC_STAT, &mds);
printf("Старые права доступа: %on", mds.shm_perm.mode);
sscanf(mode, "%o", &mds.shm_perm.mode);
shmctl(shmid, IPC_SET, &mds);
printf("Новые права доступа: %on", mds.shm_perm.mode);
}
Использовать программу нужно так:
./shm_demo -w строка
запись строки в сегмент
./shm_demo -r
чтение строки из сегмента
./shmdemo -m права
изменение прав доступа
./shm_demo -d
удаление сегмента
Выполните команду
$ ./shm_demo -w string
А затем запустите утилиту ipcs. Вы увидите, что наша программа создала разделяемый сегмент памяти:
------ Shared Memory Segments ------
key shmid owner perms bytes nattch status
0x44063781 0 root 660 256 0
------ Semaphore Arrays ------
key semid owner perns nsems status
------ Message Queues ------
key msqid owner perms used-bytes messages
Затем выполните команду:
$ ./shm_demo -r
Вы получите информацию:
Информация из сегмента: string
Попробуем изменить права доступа, а затем просмотреть информацию командой ipcs:
$./shm_demo -m 0666
------ Shared Memory Segments ------
key shmid owner perms bytes nattch status
0x44063781 0 root 660 256 0
------ Semaphore Arrays ------