Московский физико-технический институт
Опубликован: 16.09.2004 | Доступ: свободный | Студентов: 7774 / 1593 | Оценка: 4.44 / 4.28 | Длительность: 15:33:00
Лекция 4:

Средства System V IPC. Организация работы с разделяемой памятью в UNIX. Понятие нитей исполнения (thread)

< Лекция 3 || Лекция 4: 12345 || Лекция 5 >

Разделяемая память в UNIX. Системные вызовы shmget(), shmat(), shmdt()

С точки зрения операционной системы, наименее семантически нагруженным средством System V IPC является разделяемая память (shared memory). Мы уже упоминали об этой категории средств связи на лекции. Для текущего семинара нам достаточно знать, что операционная система может позволить нескольким процессам совместно использовать некоторую область адресного пространства. Внутренние механизмы, позволяющие реализовать такое использование, будут подробно рассмотрены на лекции, посвященной сегментной, страничной и сегментно-страничной организации памяти.

Все средства связи System V IPC требуют предварительных инициализирующих действий (создания) для организации взаимодействия процессов.

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

  • Стандартный способ. В качестве значения ключа системному вызову поставляется значение, сформированное функцией ftok() для некоторого имени файла и номера экземпляра области разделяемой памяти. В качестве флагов поставляется комбинация прав доступа к создаваемому сегменту и флага IPC_CREAT . Если сегмент для данного ключа еще не существует, то система будет пытаться создать его с указанными правами доступа. Если же вдруг он уже существовал, то мы просто получим его дескриптор. Возможно добавление к этой комбинации флагов флага IPC_EXCL . Этот флаг гарантирует нормальное завершение системного вызова только в том случае, если сегмент действительно был создан (т. е. ранее он не существовал), если же сегмент существовал, то системный вызов завершится с ошибкой, и значение системной переменной errno, описанной в файле errno.h, будет установлено в EEXIST.
  • Нестандартный способ. В качестве значения ключа указывается специальное значение IPC_PRIVATE . Использование значения IPC_PRIVATE всегда приводит к попытке создания нового сегмента разделяемой памяти с заданными правами доступа и с ключом, который не совпадает со значением ключа ни одного из уже существующих сегментов и который не может быть получен с помощью функции ftok() ни при одной комбинации ее параметров. Наличие флагов IPC_CREAT и IPC_EXCL в этом случае игнорируется.

Системный вызов shmget()

Прототип системного вызова

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, int size, 
           int shmflg);

Описание системного вызова

Системный вызов shmget предназначен для выполнения операции доступа к сегменту разделяемой памяти и, в случае его успешного завершения, возвращает дескриптор System V IPC для этого сегмента (целое неотрицательное число, однозначно характеризующее сегмент внутри вычислительной системы и использующееся в дальнейшем для других операций с ним).

Параметр key является ключом System V IPC для сегмента, т. е. фактически его именем из пространства имен System V IPC. В качестве значения этого параметра может использоваться значение ключа, полученное с помощью функции ftok() , или специальное значение IPC_PRIVATE . Использование значения IPC_PRIVATE всегда приводит к попытке создания нового сегмента разделяемой памяти с ключом,который не совпадает со значением ключа ни одного из уже существующих сегментов и который не может быть получен с помощью функции ftok() ни при одной комбинации ее параметров.

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

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

  • IPC_CREAT – если сегмента для указанного ключа не существует, он должен быть создан;
  • IPC_EXCL – применяется совместно с флагом IPC_CREAT . При совместном их использовании и существовании сегмента с указанным ключом, доступ к сегменту не производится и констатируется ошибочная ситуация, при этом переменная errno, описанная в файле <errno.h>, примет значение EEXIST ;
  • 0400 – разрешено чтение для пользователя, создавшего сегмент;
  • 0200 – разрешена запись для пользователя, создавшего сегмент;
  • 0040 – разрешено чтение для группы пользователя, создавшего сегмент;
  • 0020 – разрешена запись для группы пользователя, создавшего сегмент;
  • 0004 – разрешено чтение для всех остальных пользователей;
  • 0002 – разрешена запись для всех остальных пользователей.

Возвращаемое значение

Системный вызов возвращает значение дескриптора System V IPC для сегмента разделяемой памяти при нормальном завершении и значение -1 при возникновении ошибки.

Доступ к созданной области разделяемой памяти в дальнейшем обеспечивается ее дескриптором, который вернет системный вызов shmget() . Доступ к уже существующей области также может осуществляться двумя способами:

  • Если мы знаем ее ключ, то, используя вызов shmget() ,можем получить ее дескриптор. В этом случае нельзя указывать в качестве составной части флагов флаг IPC_EXCL , а значение ключа, естественно, не может быть IPC_PRIVATE . Права доступа игнорируются, а размер области должен совпадать с размером, указанным при ее создании.
  • Либо мы можем воспользоваться тем, что дескриптор System V IPC действителен в рамках всей операционной системы, и передать его значение от процесса, создавшего разделяемую память, текущему процессу. Отметим, что при создании разделяемой памяти с помощью значения IPC_PRIVATE – это единственно возможный способ.

После получения дескриптора необходимо включить область разделяемой памяти в адресное пространство текущего процесса. Это осуществляется с помощью системного вызова shmat() . При нормальном завершении он вернет адрес разделяемой памяти в адресном пространстве текущего процесса. Дальнейший доступ к этой памяти осуществляется с помощью обычных средств языка программирования.

Системный вызов shmat()

Прототип системного вызова

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
char *shmat(int shmid, char *shmaddr, 
            int shmflg);

Описание системного вызова

Системный вызов shmat предназначен для включения области разделяемой памяти в адресное пространство текущего процесса. Данное описание не является полным описанием системного вызова, а ограничивается рамками текущего курса. Для полного описания обращайтесь к UNIX Manual.

Параметр shmid является дескриптором System V IPC для сегмента разделяемой памяти, т. е. значением, которое вернул системный вызов shmget() при создании сегмента или при его поиске по ключу.

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

Параметр shmflg в нашем курсе может принимать только два значения: 0 – для осуществления операций чтения и записи над сегментом и SHM_RDONLY – если мы хотим только читать из него. При этом процесс должен иметь соответствующие права доступа к сегменту.

Возвращаемое значение

Системный вызов возвращает адрес сегмента разделяемой памяти в адресном пространстве процесса при нормальном завершении и значение -1 при возникновении ошибки.

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

Системный вызов shmdt()

Прототип системного вызова

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(char *shmaddr);

Описание системного вызова

Системный вызов shmdt предназначен для исключения области разделяемой памяти из адресного пространства текущего процесса.

Параметр shmaddr является адресом сегмента разделяемой памяти,т. е. значением, которое вернул системный вызов shmat() .

Возвращаемое значение

Системный вызов возвращает значение 0 при нормальном завершении и значение -1 при возникновении ошибки.

Прогон программ с использованием разделяемой памяти

Для иллюстрации использования разделяемой памяти давайте рассмотрим две взаимодействующие программы.

/* Программа 1 (06-1а.с) для иллюстрации работы с разделяемой памятью */ 
/* Мы организуем разделяемую память для массива из трех целых чисел. 
Первый элемент массива является счетчиком числа запусков программы 1, 
т. е. данной программы, второй элемент массива – счетчиком числа запусков
программы 2, третий элемент массива – счетчиком числа запусков обеих 
программ */ 
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <errno.h>
int main()
{
    int *array;    /* Указатель на разделяемую память */
    int shmid;     /* IPC дескриптор для области разделяемой памяти */
    int new = 1;   /* Флаг необходимости инициализации элементов массива */
    char pathname[] = "06-1a.c"; /* Имя файла, 
        используемое для генерации ключа. Файл с таким 
        именем должен существовать в текущей директории */
    key_t key;     /* IPC ключ */ 
    /* Генерируем IPC ключ из имени файла 06-1a.c в 
        текущей директории и номера экземпляра области 
        разделяемой памяти 0 */
    if((key = ftok(pathname,0)) < 0){
        printf("Can\'t generate key\n");
        exit(-1);
    }
    /* Пытаемся эксклюзивно создать разделяемую память для 
    сгенерированного ключа, т.е. если для этого ключа она
    уже существует, системный вызов вернет отрицательное 
    значение. Размер памяти определяем как размер массива 
    из трех целых переменных, права доступа 0666 – чтение
    и запись разрешены для всех */
    if((shmid = shmget(key, 3*sizeof(int),             
    0666|IPC_CREAT|IPC_EXCL)) < 0){
    /* В случае ошибки пытаемся определить: возникла ли она
    из-за того, что сегмент разделяемой памяти уже существует
    или по другой причине */
        if(errno != EEXIST){
            /* Если по другой причине – прекращаем работу */
            printf("Can\'t create shared memory\n");
            exit(-1);
        } else {
            /* Если из-за того, что разделяемая память уже 
            существует, то пытаемся получить ее IPC 
            дескриптор и, в случае удачи, сбрасываем флаг 
            необходимости инициализации элементов массива */

            if((shmid = shmget(key, 3*sizeof(int), 0)) < 0){
                printf("Can\'t find shared memory\n");
                exit(-1);
            }
            new = 0;
        }
    }
    /* Пытаемся отобразить разделяемую память в адресное 
    пространство текущего процесса. Обратите внимание на то,
    что для правильного сравнения мы явно преобразовываем 
    значение -1 к указателю на целое.*/ 

    if((array = (int *)shmat(shmid, NULL, 0)) == (int *)(-1)){
        printf("Can't attach shared memory\n");
        exit(-1);
    }
    /* В зависимости от значения флага new либо 
инициализируем массив, либо увеличиваем 
соответствующие счетчики */ 
    if(new){
        array[0] = 1;
        array[1] = 0;
        array[2] = 1;
    } else {
        array[0] += 1;
        array[2] += 1;
    }
    /* Печатаем новые значения счетчиков, удаляем 
    разделяемую память из адресного пространства 
    текущего процесса и завершаем работу */
    printf("Program 1 was spawn %d times, 
        program 2 - %d times, total - %d times\n",
        array[0], array[1], array[2]);
    if(shmdt(array) < 0){ 
        printf("Can't detach shared memory\n");
        exit(-1);
    }
    return 0;
}
Листинг 6.1a. Программа 1 (06-1а.с) для иллюстрации работы с разделяемой памятью
/* Программа 2 (06-1b.с) для иллюстрации работы с 
разделяемой памятью */ 
/* Мы организуем разделяемую память для массива из
трех целых чисел. Первый элемент массива является 
счетчиком числа запусков программы 1, т. е. данной 
программы, второй элемент массива – счетчиком числа
запусков программы 2, третий элемент массива – 
счетчиком числа запусков обеих программ */ 
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <errno.h>
int main()
{
    int *array;   /* Указатель на разделяемую память */
    int shmid;    /* IPC дескриптор для области разделяемой памяти */
    int new = 1;  /* Флаг необходимости инициализации элементов массива */
    char pathname[] = "06-1a.c"; /* Имя файла, 
        используемое для генерации ключа. Файл с таким 
        именем должен существовать в текущей директории */
    key_t key;    /* IPC ключ */ 
    /* Генерируем IPC ключ из имени файла 06-1a.c в 
    текущей директории и номера экземпляра области 
    разделяемой памяти 0 */
    if((key = ftok(pathname,0)) < 0){
        printf("Can\'t generate key\n");
        exit(-1);
    }
    /* Пытаемся эксклюзивно создать разделяемую память
    для сгенерированного ключа, т.е. если для этого 
    ключа она уже существует, системный вызов вернет
    отрицательное значение. Размер памяти определяем 
    как размер массива из трех целых переменных, права 
    доступа 0666 – чтение и запись разрешены для всех */
    if((shmid = shmget(key, 3*sizeof(int), 
        0666|IPC_CREAT|IPC_EXCL)) < 0){
    /* В случае возникновения ошибки пытаемся определить: 
    возникла ли она из-за того, что сегмент разделяемой 
    памяти уже существует или по другой причине */
        if(errno != EEXIST){
            /* Если по другой причине – прекращаем работу */
            printf("Can\'t create shared memory\n");
            exit(-1);
        } else {
            /* Если из-за того, что разделяемая память уже
            существует, то пытаемся получить ее IPC дескриптор
            и, в случае удачи, сбрасываем флаг необходимости
            инициализации элементов массива */
            if((shmid = shmget(key, 3*sizeof(int), 0)) < 0){
                printf("Can\'t find shared memory\n");
                exit(-1);
            }
            new = 0;
        }
    }
    /* Пытаемся отобразить разделяемую память в адресное 
    пространство текущего процесса. Обратите внимание на то,
    что для правильного сравнения мы явно преобразовываем 
    значение -1 к указателю на целое.*/ 
    if((array = (int *)shmat(shmid, NULL, 0)) == 
        (int *)(-1)){
        printf("Can't attach shared memory\n");
        exit(-1);
    }
    /* В зависимости от значения флага new либо 
    инициализируем массив, либо увеличиваем 
    соответствующие счетчики */ 
    if(new){
        array[0] = 0;
        array[1] = 1;
        array[2] = 1;
    } else {
        array[1] += 1;
        array[2] += 1;
    }
    /* Печатаем новые значения счетчиков, удаляем разделяемую
    память из адресного пространства текущего процесса и 
    завершаем работу */
    printf("Program 1 was spawn %d times, 
        program 2 - %d times, total - %d times\n",
    array[0], array[1], array[2]);
    if(shmdt(array) < 0){ 
        printf("Can't detach shared memory\n");
        exit(-1);
    }
    return 0;
}
Листинг 6.1b. Программа 2 (06-1b.с) для иллюстрации работы с разделяемой памятью

Эти программы очень похожи друг на друга и используют разделяемую память для хранения числа запусков каждой из программ и их суммы. В разделяемой памяти размещается массив из трех целых чисел. Первый элемент массива используется как счетчик для программы 1, второй элемент – для программы 2, третий элемент – для обеих программ суммарно. Дополнительный нюанс в программах возникает из-за необходимости инициализации элементов массива при создании разделяемой памяти. Для этого нам нужно, чтобы программы могли различать случай, когда они создали ее, и случай, когда она уже существовала. Мы добиваемся различия, используя вначале системный вызов shmget() с флагами IPC_CREAT и IPC_EXCL . Если вызов завершается нормально, то мы создали разделяемую память. Если вызов завершается с констатацией ошибки и значение переменной errno равняется EEXIST, то, значит, разделяемая память уже существует, и мы можем получить ее IPC дескриптор, применяя тот же самый вызов с нулевым значением флагов. Наберите программы, сохраните под именами 06-1а.с и 06-1b.c cоответственно, откомпилируйте их и запустите несколько раз. Проанализируйте полученные результаты.

< Лекция 3 || Лекция 4: 12345 || Лекция 5 >
лия логовина
лия логовина

организовать двустороннюю поочередную связь процесса-родителя и процесса-ребенка через pipe, используя для синхронизации сигналы sigusr1 и sigusr2.

Макар Оганесов
Макар Оганесов