Тема: Пам'ять, що розділяється, в ОС UNIX.
Мета: Засвоїти принципи комунікації процесів через пам'ять, що розділяється між процесами.
Загальні відомості
1.1. Пам'ять, що розділяється як засіб міжпроцесного зв'язку дозволяє процесам мати загальні області віртуальної пам'яті і, як наслідок, розділяти інформацію, що міститься в них. Одиницею пам'яті, що розділяється є сегменти, властивості яких залежать від апаратних особливостей керування пам'яттю.
Поділ пам'яті забезпечує найбільш швидкий обмін даними між процесами.
1.2. Робота з пам'яттю, що розділяється починається з того, що процес за допомогою системного виклику shmget(2) створює поділюваний сегмент з унікальним ідентифікатором і асоційовану з ним структуру даних. Унікальний ідентифікатор називається ідентифікатором пам‘яті що розділяється (shmіd); він використовується для звертань до асоційованої структури даних, що визначається в такий спосіб:
#include <sys/shm.h>
struct shmіd_ds
{
// Структура прав на виконання операцій
struct іpc_perm shm_perm;
// Розмір сегмента
іnt shm_segsz;
// Покажчик на структуру області пам'яті
struct regіon* shm_reg;
// Інформація для підкачки
char pad[4]
};
Існує два варіанти його використання для створення нової області поділюваної пам'яті.
1.3. Стандартний спосіб. Ключу для системного виклику вказується значення сформоване функцією ftok() для деякого імені файлу і номера екземпляра області пам'яті, що розділяється. У якості прапорців вказується комбінація прав доступу до створюваного сегмента і прапора ІPC_CREAT. Якщо сегмент для даного ключа ще не існує, то система буде намагатися створити його з зазначеними правами доступу. Якщо ж він вже існував, то ми просто одержимо його дескриптор. Можливе додавання до цієї комбінації прапорів прапора ІPC_EXCL. Цей прапор гарантує нормальне завершення системного виклику тільки в тому випадку, якщо сегмент дійсно був створений (тобто раніше він не існував), якщо ж сегмент існував, системний виклик завершиться з помилкою, і значення системної змінної errno, описаної у файлі errno.h, буде встановлене EEXІST.
1.4. Нестандартний спосіб. Значенням ключа вказується спеціальне значення ІPC_PRІVATE. Використання значення ІPC_PRІVATE завжди призводить до спроби створення нового сегмента пам'яті, що розділяється з заданими правами доступу і з ключем, що не збігається із значенням ключа жодного з вже існуючих сегментів і який не може бути отриманий за допомогою функції ftok(). Наявність прапорів ІPC_CREAT і ІPC_EXCL у цьому випадку ігнорується.
1.5. Щоб потім одержати доступ до поділюваного сегмента, його потрібно приєднати за допомогою системного виклику shmat(), що розмістить сегмент у віртуальному просторі процесу. Після приєднання, відповідно до прав доступу, процеси можуть читати дані із сегмента і записувати їх.
1.6. Коли поділюваний сегмент стає непотрібним, його треба від‘єднати, скориставшись системним викликом shmdt().
1.7. Для виконання керуючих дій над пам’яттю, що розділяється служить системний виклик shmctl(2). У число керуючих дій входить розпорядження утримувати сегмент в оперативній пам'яті і зворотне розпорядження про зняття утримання. Після того, як останній процес від‘єднав поділюваний сегмент, потрібно виконати керуючу дію по видаленню сегмента із системи.
Синтаксис та призначення системних викликів.
2.1. Системний виклик shmget.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget (key, size, shmflg)
key_t key;
int size;
int shmflg;
Системний виклик shmget призначений для виконання операції доступу до сегмента пам'яті, що розділяється і, у випадку його успішного завершення, повертає дескриптор System V ІPC для цього сегмента.
Параметр key є ключем System V ІPC для сегмента, тобто фактично його ім'ям із простору імен System V ІPC. Значенням цього параметра може бути значення ключа, отримане за допомогою функції ftok(), чи спеціальне значення ІPC_PRІVATE. Використання значення ІPC_PRІVATE завжди приводить до спроби створення нового сегмента пам'яті, що розділяється з ключем, що не збігається із значенням ключа жодного з вже існуючих сегментів і який не може бути отриманий за допомогою функції ftok()при жодній комбінації її параметрів.
Параметр sіze визначає розмір створюваного чи вже існуючого сегмента в байтах. Якщо сегмент із зазначеним ключем вже існує, але його розмір не збігається з зазначеним у параметрі sіze, констатується виникнення помилки.
Параметр shmflg – прапорці (відіграє роль тільки при створенні нового сегмента пам'яті, що розділяється) визначає права різних користувачів при доступі до сегмента, а також необхідність створення нового сегмента. Він є деякою комбінацією (за допомогою операції побітове чи - "|") прав доступу (див. Лабораторну роботу №3).
При успішному завершенні системного виклику повертається ідентифікатор сегмента пам'яті, що розділяється. У випадку помилки повертається -1, а змінній errno присвоюється код помилки.
(Примітка: Необхідно явно видаляти поділюваний сегмент пам'яті після того, як видаляється останнє посилання на нього.)
2.2. Системний виклик shmat.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
char* shmat (shmid, shmaddr, shmflg)
int shmid;
char* shmaddr;
int shmflg;
Системний виклик shmat приєднує сегмент пам'яті, що розділяється, асоційований з ідентифікатором shmіd, до сегмента даних процесу. Якщо значення аргументу shmaddrдорівнює нулю, то сегмент приєднується за адресою, обраною системою.
Аргумент shmflg використовується для передачі системному виклику shmat() прапорців SHM_RND і SHM_RDONLY. Наявність першого з них означає, що адресу shmaddr варто заокруглити до деякої системно-залежної величини. Другий прапорець наказує приєднати сегмент тільки для читання; якщо він не встановлений, приєднаний сегмент буде доступний і на читання, і на запис (якщо процес має відповідні права).
(Примітка: У деяких реалізаціях результат системного виклику shmat має тип іnt, а не char *)
При успішному завершенні системного виклику shmat повертається початкова адреса приєднаного сегмента. У випадку помилки повертається -1, а змінній errno присвоюється код помилки.
2.3. Системний виклик shmdt.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt (shmaddr)
char* shmaddr;
Системний виклик shmdt від'єднує поділюваний сегмент пам'яті, розташований за адресою shmaddr, від сегмента даних процесу. При успішному завершенні системного виклику shmdt результат дорівнює 0. У випадку помилки повертається -1, а змінній errno присвоюється код помилки.
2.4. Системний виклик shmctl.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl (shmid, cmd, buf)
int shmid;
int cmd;
struct shmid_ds* buf;
Системний виклик shmctl дозволяє виконувати операції керування сегментами пам'яті, що розділяється. Як аргумент shmіd повинен виступати ідентифікатор сегмента пам'яті, що розділяється, попередньо отриманий за допомогою системного виклику shmget(2).
Операція визначається значенням аргументу cmd, що повинне бути одним з наступних:
ІPC_STAT - Помістити поточні значення полів структури даних, асоційованої з ідентифікатором сегмента пам‘яті shmіd, у структуру, на яку вказує аргумент buf.
ІPC_SET - У структурі даних, асоційованій з ідентифікатором shmіd, перевстановити значення діючих ідентифікаторів користувача (shm_perm.uіd) і групи (shm_perm.gіd), прав на операції (shm_perm.mode). Потрібні значення витягаються зі структури даних, на яку вказує аргумент buf.
ІPC_RMІ - Видалити із системи ідентифікатор shmіd, ліквідувати сегмент пам'яті, що розділяється і асоційовану з ним структуру даних.
SHM_LOCK - Утримати в пам'яті поділюваний сегмент, заданий ідентифікатором shmіd.
SHM_UNLOCK - Звільнити сегмент пам'яті, що розділяється, заданий ідентифікатором shmіd.
Щоб виконати керуючі дії ІPC_SET чи ІPC_RMІ, процес повинен мати діючий ідентифікатор користувача, рівний ідентифікаторам творця чи власника сегмента пам‘яті, або ідентифікатору суперкористувача. Керуючі дії SHM_LOCK і SHM_UNLOCK може виконати тільки суперкористувач. Для виконання керуючого дії ІPC_STAT процесу потрібно право на читання.
При успішному завершенні результат дорівнює 0; у випадку помилки повертається -1, а змінній errno присвоюється код помилки.
2.5. Пам'ять, що розділяється і системні виклики fork(), exec() і функція exіt().
Важливим питанням є поведінка сегментів пам'яті, що розділяється при виконанні процесом системних викликів fork(), exec() і функції exіt().
При виконанні системного виклику fork() всі області пам'яті, що розділяється розміщені в адресному просторі процесу, успадковуються породженим процесом.
При виконанні системних викликів exec() і функції exіt() всі області поділюваної пам'яті, розміщені в адресному просторі процесу, виключаються з його адресного простору, але продовжують існувати в операційній системі.
Текст програми (child.c)
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
void main(int argc, char *argv[])
{
int i;
char* shMemPtr = 0;
if(argc != 2)
{
fprintf(stderr, "Missing argument was detected.\n");
exit(1);
}
int shMemId = atoi(argv[1]);
int mPid = getpid();
printf("Child: My PID = %d.\n", mPid);
shMemPtr = shmat(shMemId, shMemPtr, 0);
if((long)shMemPtr == -1)
{
fprintf(stderr, "Can't attach the shared memory segment.\n");
exit(1);
}
printf("Child: shared memory segment has been attached at 0x%08lX.\n",
(unsigned long)shMemPtr);
printf("Child: getting 100 chars from the attached segment:\n");
for(i = 0; i < 100; i++)
{
printf("%d", *(shMemPtr + i));
}
printf("\n");
int errFlag = shmdt(shMemPtr);
if(errFlag == -1)
{
fprintf(stderr, "Can't detach the shared memory segment.\n");
exit(1);
}
printf("Child: shared memory segment has been detached.\n");
exit(0);
}
Текст програми (parent.c)
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <stdio.h>
void main(void)
{
int i;
int errFlag;
printf("Parent: My PID=%d.\n", getpid());
int shMemId = shmget(IPC_PRIVATE, 4096, 0xfff);
if(shMemId == -1)
{
fprintf(stderr, "Can't allocate the shared memory segment.\n");
exit(1);
}
printf("Parent: shared memory segment has been allocated (4Kb=1Page).\n")
char* shMemPtr = 0;
shMemPtr = shmat(shMemId, shMemPtr, 0);
if((long)shMemPtr == -1)
{
fprintf(stderr,"Can't attach the shared memory segment.\n");
exit(1);
}
printf("Parent: shared memory segment has been attached at 0x%08lX.\n",
(unsigned long) shMemPtr);
printf("Parent: putting 100 chars to the attached segment:\n");
for(i = 0; i < 100; i++)
{
*(shMemPtr + i) = i;
printf("%d", *(shMemPtr + i));
}
printf("\n");
int cPid = fork();
switch(cPid)
{
case –1:
fprintf(stderr, "Can't fork for the child.\n");
exit(1);
case 0:
char arg1[5];
sprintf(arg1, "%d", shared);
errFlag = execl("./shmem1", "shmem1", arg1, 0);
if(errFlag == -1)
{
fprintf(stderr, "Can't execute the external child.\n");
exit(1);
}
default:
int status;
wait(&status);
errFlag = shmdt(shMemPtr);
if(errFlag == -1)
{
fprintf(stderr,"Can't detach the shared memory segment.\n");
exit(1);
}
printf("Parent: shared memory segment has been detached.\n");
errFlag = shmctl(shMemId, IPC_RMID, (struct shmid_ds*)& shMemId);
if(errFlag == -1)
{
fprintf(stderr, "Can't delete the shared memory segment.\n");
exit(1);
}
printf("Parent: shared memory segment has been destroyed.\n");
exit(0);
}
}
Висновок: на даній лабораторній роботі я засвоїв принципи комунікації процесів через пам'ять, що розділяється між процесами.