Общая (совместно используемая) память

Один из самых простых методов межпроцессового взаимодействия - использовать общую память. Общая память позволяет двум или более процессам обращаться к одной и той же области памяти, как будто они все вызывали malloc и им были возвращены указатели на одну и ту же физическую память. Когда один процесс изменяет память, все другие процессы "видят" модификацию.

Быстрое локальное взаимодействие

Общая память - самая быстрая форма межпроцессового взаимодействия, потому что все процессы совместно используют одну иту же часть памяти. Доступ к этой общей памяти осуществляется с той же скоростью, что и при обращении к несовместно используемой памяти, и это не требует системного вызова или входа в ядро. Это также не требует излишнего копирования данных.

Поскольку ядро не синхронизирует доступы к совместно используемой памяти, вы должны сами обеспечить синхронизацию. Например, процесс не должен читать из памяти, пока данные не записаны туда, и два процесса не должны написать по одному и тому же адресу памяти в одно и то же время. Общая стратегия избежания условий гонки состоит в том, чтобы использовать семафоры.

Модель памяти

Чтобы использовать сегмент общей памяти, один процесс должен выделить сегмент. Тогда каждый процесс, желающий обращаться к сегменту должен подключить сегмент. После окончания его использования сегмента, каждый процесс отключает сегмент. В некоторый момент, один процесс должен освободить сегмент.

Понимание модели памяти Linux помогает объяснить процесс выделения и подключения. Под Linux, виртуальная память каждого процесса разбита на страницы. Каждый процесс поддерживает отображение его адресов памяти на эти страницы виртуальной памяти, которые содержат фактические данные. И хотя каждый процесс имеет собственные адреса, отображения многих процессов могут указывать на одну и ту же страницу, разрешая совместное использование памяти.

Выделение нового сегмента общей памяти приводит к созданию страницы виртуальной памяти. Поскольку все процессы желают обратиться к одному и тому же общему сегменту, то только один процесс должен выделить новый общий сегмент. Выделение существующего сегмента не создает новых страниц, а возвращает идентификатор для существующих. Чтобы разрешить процессу использовать сегмент общей памяти, процесс подключает сегмент, который добавляет отображение его виртуальной памяти на общедоступные страницы сегмента. Когда работа с сегментом завершена, эти отображения удаляются. Когда ни одни из процессов не хочет обращаться к сегментам общей памяти, какой-то один процесс должен освободить страницы виртуальной памяти. Все сегменты общей памяти выделяются постранично и округляются до размера страницы системы, который является числом байтов в странице памяти. На системах Linux, размер страницы равен 4 КБ, но вы должны получить это значение, вызывая функцию getpagesize.

Выделение

Процесс выделяет сегмент общей памяти, используя shmget ("SHared Memory GET"). Его первый параметр - целочисленный ключ, который определяет, какой сегмент создать. Несвязанные процессы могут обращаться к одному и тому же сегменту, используя одно и то же ключевое значение. К сожалению, другие процессы, возможно, также выбрали тот же самый ключ, что может привести к конфликту. Используя специальную константу IPC_PRIVATE как ключевое значение, гарантируется, что создастся совершенно новый сегмент памяти.

Его второй параметр определяет число байтов в сегменте. Поскольку сегменты выделяются постранично, число фактически выделенных байт округляется до размера страницы.

Третий параметр - поразрядное двоичное или значений флажка, которые определяют опции к shmget. Значения флажка включают такие параметры:

Например, этот вызов shmget создает новый сегмент общей памяти (или обращается к существующему, если shm_key уже используется), с правами на читение и запись владельцем, но не другими пользователями.

int segment_id = shmget (shm_key, getpagesize (), IPC_CREAT | S_IRUSR | S_IWUSER);

Если вызов успешен, shmget возвращает идентификатор сегмента. Если сегмент общей памяти уже существует, то права на доступ проверены, и проверка гарантирует, что сегмент не отмечен для удаления.

Подключение и отключение

Чтобы сделать сегмент общей памяти доступным, процесс должен использовать shmat, "SHared Memory ATtach" Передайте ему идентификатор сегмента общей памяти SHMID, возвращенный shmget. Второй параметр - указатель, который определяет, где в адресном пространстве вашего процесса вы хотите отобразить общую память; если вы передадите NULL, то Linux выберет любой доступный адрес. Третий параметр - флажок, который может включить следующие параметры:

Если вызов успешен, он вернет адрес подключенного общего сегмента. Потомки, созданные вызовами fork, наследуют подключенные общие сегменты; они могут отключить сегменты общей памяти, если захотят.

Когда вы закончили работу с сегментом общей памяти, сегмент должен быть отключен, используя shmdt ("SHared Memory DeTach"). Передайте ему адрес, возвращенный shmat. Если сегмент был освобожден, и больше не осталось процессов, использующих его, он будет удален. Вызовы exit и exec автоматически отключают сегменты.

Управление и освобождение общей памяти

Shmctl ("SHared Memory ConTrol") вызов возвращает информацию об сегменте общей памяти и может изменить его.Первый параметр - идентификатор сегмента общей памяти.

Чтобы получить информацию о сегменте общей памяти, передайте IPC_STAT как второй параметр и указатель на struct shmid_ds.

Чтобы удалить сегмент, передайте IPC_RMID как второй параметр, и передайте NULL как третий параметр. Сегмент удален, когда последний процесс, который подключил его, отключит его.

Каждый сегмент общей памяти должен быть явно освобожден, используя shmctl, когда Вы закончили работу с ним, чтобы избежать нарушение системного предела размера количества сегментов общей памяти. Вызовы exit и exec отключат сегменты памяти, но не освобождят их.

Смотри shmctl man-страницу для описания других операций, которые можно выполнять с сегментами общей памяти.

Пример программы

Программа листинга 5.1 иллюстрирует использование общей памяти.

Листинг 5.1 (shm.c)

#include <stdio.h>
#include <sys/shm.h>
#include <sys/stat.h>
	
int main (){
	int segment_id;
	char* shared_memory;
	struct shmid_ds shmbuffer;
	int segment_size;
	const int shared_segment_size = 0x6400;
	
	/* Выделить сегмент  общей памяти. */
	segment_id = shmget (IPC_PRIVATE, shared_segment_size, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
	
	/* Подключить сегмент общей памяти.  */
	shared_memory = (char*) shmat (segment_id, 0, 0);
	printf ("Общая память подключена по адресу %p\n", shared_memory);
	
	/* Определить размер сегмента. */
	shmctl (segment_id, IPC_STAT, &shmbuffer);
	segment_size = shmbuffer.shm_segsz;
	printf ("Размер сегмента: %d\n", segment_size);
	
	/* Написать строку в сегмент общей памяти. */
	sprintf (shared_memory, "Hello, world.");
	
	/* Отключить сегмент общей памяти. */ 
	shmdt (shared_memory);
	
	/* Снова подключить сегмент общей памяти, по различным адресам. */ 
	shared_memory = (char*) shmat (segment_id, (void*) 0x5000000, 0);
	printf ("Общая память переподключена по адресу %p\n", shared_memory);
	
	/* Распечатать строку из общей памяти.  */ 
	printf ("%s\n", shared_memory);
	
	/* Отключить сегмент общей памяти. */ 
	shmdt (shared_memory);
	
	/* Освободить сегмент общей памяти.  */
	shmctl (segment_id, IPC_RMID, 0);
	return 0;
}

Отладка

Команда ipcs предоставляет информацию относительно средств взаимодействия процессов, включая общие сегменты памяти. Используйте флаг -m, чтобы получить информацию об общей памяти. Например, этот код иллюстрирует что сегмент общей памяти, пронумерованный 1627649, находится в использовании:

% ipcs -m
------ Shared Memory Segments --------
key	    shmid	owner	perms	bytes	nattch	status
0x00000000  1627649	user	640	25600	0

Если этот сегмент памяти был ошибочно оставлен программой, вы можете использовать команду ipcrm, чтобы удалить его.

% ipcrm shm 1627649

За и против

Cегменты общей памяти позволяют осуществлять быструю двунаправленную связь среди любого числа процессов. Каждый пользователь может и читать и писать, но программа должна установить и следовать некоторому протоколу для того, чтобы предотвратить условия гонки типа перезаписи информации прежде, чем она прочитается. К сожалению, Linux строго не гарантирует эксклюзивный доступ даже если вы создадите новый общий сегмент с IPC_PRIVATE.

Кроме того, для того чтоб несколько процессов могли использовать общую память, они должны принять меры, чтобы не использовать один и тот же ключ.