В ОС UNIX процеси виконуються у власних адресних просторах і є ізольовані один від одного; тим самим зведено до мінімуму можливості впливу процесів один на одного, що є надто важливим у багатозадачних ОС, однак власне концепція UNIX ґрунтується на модульному принципі й передбачає взаємодію поміж процесами.
Взаємодія поміж процесами необхідна для розв’язування таких завдань:
1 Передавання даних; їхній обсяг може коливатись від десятків байтів до кількох мегабайтів.
2 Спільне використання даних; процеси можуть використовувати спільно одну копію даних, причому зміни, внесені одним процесом, відразу будуть помітні для іншого. Кількість взаємодіючих процесів може бути більшою за два. З метою збереження цілісності ресурсів процесам може потребуватись протокол взаємодії для збереження цілісності даних та виключення конфліктів при доступі до них.
3 Повідомлення використовуються, коли один процес має сповістити інший про певну подію, наприклад для синхронізування кількох процесів. Розв’язується ця задача засобами ОС, тому що власне самі процеси у рамцях багатозадачної ОС діяли б неефективно і навіть небезпечно.
До засобів міжпроцесної взаємодії, притаманних усім версіям UNIX, належать:
▬ сигнали;
▬ канали;
▬ іменовані канали FIFO;
▬ повідомлення та їх черги;
▬ семафори;
▬ розподілювана пам’ять;
▬ сокети.
Використання сигналів обмежується сферою повідомлень про помилки, але вони можуть використовуватись і для синхронізування процесів, передавання простіших команд від одного процесу до іншого. Сигнали є надто ресурсоємні, малоінформативні й їхню кількість обмежено.
Канали може бути зорганізовано при роботі у командному рядку shell:
cat myfile |wc.
Стандартне виведення програми cat(1), яка виводить вміст файла myfile, передається на стандартний ввід програми wc(1), яка, у свою чергу, підраховує кількість рядків, слів та символів. Як наслідок на екран монітора буде виведено дані:
12 45 260,
що означає кількість рядків, слів та символів у файлі myfile. Отже, два процеси обмінялися даними. Програмний канал забезпечує односпрямоване передавання даних поміж двома процесами.
Для створення каналу використовується системний виклик pipe(2)
int pipe(int *filedes),
який повертає два файлових дескриптори: filedes[0] — для запису до каналу та filedes[1] — для читання з каналу. Якщо один процес записує дані до filedes[0], інший може отримати ці дані з filedes[1]. При створюванні процесу атрибути батьківського процесу наслідуються дочірнім процесом, у тому числі файлові дескриптори. Доступ до дескрипторів filedes каналу може отримати сам процес, що він викликав pipe(2), та його дочірні процеси. Отже, канали може бути використано для передавання даних лише поміж родинними процесами, а для міжпроцесної взаємодії поміж незалежними процесами не використовуються.
У наведеному прикладі обидва процеси, cat(1) та wc(1), створюються процесом shell і тому є родинними, хоча на перший погляд здаються незалежними.
Повідомлення та їхні черги є складовою частиною UNIX System V, вони обслуговуються ОС, розміщуються в адресному просторі ядра і є розподілюваним системним ресурсом. Кожна черга має свій унікальний ідентифікатор, а процеси можуть записувати та зчитувати повідомлення з різних черг. Процес, що він надсилає повідомлення до черги, може не очікувати на читання цього повідомлення певним іншим процесом. Він може завершити своє виконання, залишивши у черзі повідомлення, яке буде прочитане іншим процесом пізніше. Це надає можливість процесам обмінюватись структурованими даними, які мають атрибути:
▬ тип повідомлення; повідомлення в одній черзі можуть бути мультиплексовані;
▬ довжина даних повідомлення у байтах (може бути нульовою);
▬ власне дані; за ненульової довжини вони можуть бути структурованими.
Процес може розміщувати у черзі повідомлення за допомогою функції msgsnd(2), отримувати повідомлення певного типу з черги за допомогою msgrcv(2), керувати повідомленнями за допомогою функції msgctl(2).
Типова є ситуація взаємодії процесів, коли серверний процес обмінюється даними з кількома клієнтами, виглядає таким чином: в одній черзі можна мультиплексувати повідомлення від різних процесів, що дозволяє виконувати для обміну одну чергу, але кожному з повідомлень, спрямованих від будь-якого клієнта серверові, треба надавати значення типу, наприклад, 1; якщо в тілі повідомлення клієнт певним чином ідентифікує себе, наприклад передає свій PID, то сервер може переспрямовувати повідомлення конкретному клієнтові, привласнюючи тип повідомлення дорівнюваним цьому ідентифікаторові, функція msgrcv(2) дозволяє приймати повідомлення певного типу, тому сервер прийматиме повідомлення з типом 1, а клієнти — повідомлення з типами, що вони дорівнюють ідентифікаторам їхніх клієнтів.
Семафори використовуються для синхронізування доступу кількох процесів до розподілюваних ресурсів. Вони виконують функцію дозволу або заборони процесу використання того чи того розподілюваного ресурсу. Семафори є системним ресурсом, дії над яким зреалізовуються через інтерфейс системних функцій і не призначені для обміну потужними обсягами даних. UNIX припускає три можливі операції над семафором, що вони визначаються системним викликом semop(2), а саме його полем semop
int semop(int semid, struct sembuf *semop, size_t nops):
1 якщо величина semop додатна, поточне значення семафора збільшується на цю величину;
2 якщо значення semop дорівнює нулю, процес очікує, допоки семафор не обнулиться;
3 якщо значення semop є від’ємне, процес очікує, допоки значення семафора не збільшиться або не дорівнюватиме абсолютному значенню semop, після чого це значення віднімається від значення семафора.
Перша операція змінює значення семафора безумовно, друга — лише перевіряє його значення, тобто зреалізовується умовне виконання, третя — перевіряє й змінює значення семафора — умовне виконання операції. Наведений приклад є прикладом кооперативно виконуваних семафорів як функцій ОС: взаємодіючі процеси мають домовлятися щодо їхнього використання. ОС не накладає жодних обмежень на використання семафорів, зокрема процеси самі вирішують, яке значення семафора є таким, що дозволяє, на яку величину зміниться значення семафора тощо. Інтенсивний обмін даними поміж процесами за допомогою каналів та черг повідомлень може зменшити продуктивність системи. Це пов’язано з тим, що дані, передавані за допомогою цих об’єктів, копіюються з буфера передавального процесу у буфер ядра, а потім — у буфер приймального процесу.
Розподілювану пам’ять зорганізовано у такий спосіб, який надає двом чи більшій кількості процесів можливість безпосереднього доступу до однієї області пам’яті для обміну даними. Безумовно, процеси мають попередньо “домовитись” щодо правил використання розподілюваної пам’яті. Наприклад, допоки один з процесів записує дані до розподілюваної пам’яті, решта процесів мають утримуватись від роботи з нею. Завдання кооперативного використовування розподілюваної пам’яті розв’язується за допомогою синхронізування виконання процесів засобами семафорів. Сценарій роботи з розподілюваною пам’яттю має вигляд:
1 Сервер отримує доступ до розподілюваної пам’яті, використовуючи семафор.
2 Сервер записує дані у розподілювану пам’ять.
3 Після завершення запису сервер звільнює розподілювану пам’ять за допомогою семафора.
4 Клієнт отримує доступ до розподілюваної пам’яті й закриває доступ до ресурсу за допомогою семафора.
5 Клієнт читає дані з розподілюваної пам’яті, відкриваючи доступ до ресурсу, використовуючи семафор.
Кілька процесів можуть відбивати області розподілюваної пам’яті на різні ділянки власного віртуального адресного простору. Розподілювана пам’ять є найбільш швидкий та найменш ресурсоємний спосіб взаємодії процесів.
Сокети — це спеціальні об’єкти, які позначають комунікаційний вузол, котрий забезпечує приймання та передавання даних для процесу.