организовать двустороннюю поочередную связь процесса-родителя и процесса-ребенка через pipe, используя для синхронизации сигналы sigusr1 и sigusr2. |
Средства System V IPC. Организация работы с разделяемой памятью в UNIX. Понятие нитей исполнения (thread)
Разделяемая память в 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() . Доступ к уже существующей области также может осуществляться двумя способами:
- Если мы знаем ее ключ, то, используя вызов shmget() ,можем получить ее дескриптор. В этом случае нельзя указывать в качестве составной части флагов флаг IPC_EXCL , а значение ключа, естественно, не может быть IPC_PRIVATE . Права доступа игнорируются, а размер области должен совпадать с размером, указанным при ее создании.
- Либо мы можем воспользоваться тем, что дескриптор System V IPC действителен в рамках всей операционной системы, и передать его значение от процесса, создавшего разделяемую память, текущему процессу. Отметим, что при создании разделяемой памяти с помощью значения IPC_PRIVATE – это единственно возможный способ.
После получения дескриптора необходимо включить область разделяемой памяти в адресное пространство текущего процесса. Это осуществляется с помощью системного вызова shmat() . При нормальном завершении он вернет адрес разделяемой памяти в адресном пространстве текущего процесса. Дальнейший доступ к этой памяти осуществляется с помощью обычных средств языка программирования.
После окончания использования разделяемой памяти процесс может уменьшить размер своего адресного пространства, исключив из него эту область с помощью системного вызова shmdt() . Отметим, что в качестве параметра системный вызов shmdt() требует адрес начала области разделяемой памяти в адресном пространстве процесса, т. е. значение, которое вернул системный вызов shmat() , поэтому данное значение следует сохранять на протяжении всего времени использования разделяемой памяти.
Прогон программ с использованием разделяемой памяти
Для иллюстрации использования разделяемой памяти давайте рассмотрим две взаимодействующие программы.
/* Программа 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оответственно, откомпилируйте их и запустите несколько раз. Проанализируйте полученные результаты.