Руководство программиста для Linux

         

Разделяемая память


Основные понятия

Разделяемая память может быть наилучшим образом описана как отображение участка (сегмента) памяти, которая будет разделена между более чем одним процессом. Это гораздо более быстрая форма 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 #include #include #include #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); }


Содержание раздела