Один из самых простых методов межпроцессового взаимодействия - использовать общую память. Общая память позволяет двум или более процессам обращаться к одной и той же области памяти, как будто они все вызывали malloc и им были возвращены указатели на одну и ту же физическую память. Когда один процесс изменяет память, все другие процессы "видят" модификацию.
Общая память - самая быстрая форма межпроцессового взаимодействия, потому что все процессы совместно используют одну иту же часть памяти. Доступ к этой общей памяти осуществляется с той же скоростью, что и при обращении к несовместно используемой памяти, и это не требует системного вызова или входа в ядро. Это также не требует излишнего копирования данных.
Поскольку ядро не синхронизирует доступы к совместно используемой памяти, вы должны сами обеспечить синхронизацию. Например, процесс не должен читать из памяти, пока данные не записаны туда, и два процесса не должны написать по одному и тому же адресу памяти в одно и то же время. Общая стратегия избежания условий гонки состоит в том, чтобы использовать семафоры.
Чтобы использовать сегмент общей памяти, один процесс должен выделить сегмент. Тогда каждый процесс, желающий обращаться к сегменту должен подключить сегмент. После окончания его использования сегмента, каждый процесс отключает сегмент. В некоторый момент, один процесс должен освободить сегмент.
Понимание модели памяти Linux помогает объяснить процесс выделения и подключения. Под Linux, виртуальная память каждого процесса разбита на страницы. Каждый процесс поддерживает отображение его адресов памяти на эти страницы виртуальной памяти, которые содержат фактические данные. И хотя каждый процесс имеет собственные адреса, отображения многих процессов могут указывать на одну и ту же страницу, разрешая совместное использование памяти.
Выделение нового сегмента общей памяти приводит к созданию страницы виртуальной памяти. Поскольку все процессы желают обратиться к одному и тому же общему сегменту, то только один процесс должен выделить новый общий сегмент. Выделение существующего сегмента не создает новых страниц, а возвращает идентификатор для существующих. Чтобы разрешить процессу использовать сегмент общей памяти, процесс подключает сегмент, который добавляет отображение его виртуальной памяти на общедоступные страницы сегмента. Когда работа с сегментом завершена, эти отображения удаляются. Когда ни одни из процессов не хочет обращаться к сегментам общей памяти, какой-то один процесс должен освободить страницы виртуальной памяти. Все сегменты общей памяти выделяются постранично и округляются до размера страницы системы, который является числом байтов в странице памяти. На системах Linux, размер страницы равен 4 КБ, но вы должны получить это значение, вызывая функцию getpagesize.
Процесс выделяет сегмент общей памяти, используя shmget ("SHared Memory GET"). Его первый параметр - целочисленный ключ, который определяет, какой сегмент создать. Несвязанные процессы могут обращаться к одному и тому же сегменту, используя одно и то же ключевое значение. К сожалению, другие процессы, возможно, также выбрали тот же самый ключ, что может привести к конфликту. Используя специальную константу IPC_PRIVATE как ключевое значение, гарантируется, что создастся совершенно новый сегмент памяти.
Его второй параметр определяет число байтов в сегменте. Поскольку сегменты выделяются постранично, число фактически выделенных байт округляется до размера страницы.
Третий параметр - поразрядное двоичное или значений флажка, которые определяют опции к shmget. Значения флажка включают такие параметры:
Например, этот вызов shmget создает новый сегмент общей памяти (или обращается к существующему, если shm_key уже используется), с правами на читение и запись владельцем, но не другими пользователями.
Если вызов успешен, 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.
Кроме того, для того чтоб несколько процессов могли использовать общую память, они должны принять меры, чтобы не использовать один и тот же ключ.