Приклади реалізацій засобів гармонійної
взаємодії
Програмні канали Unix
Одним з найбільш типових засобів такого роду є труба (pipe)
або програмний канал — основний засіб взаємодії між
процесами в ОС сімейства Unix. У російськомовній літературі труби інколи
помилково називають конвеєрами. Насправді, конвеєр — це група процесів,
послідовно сполучених один з одним однонаправленими трубами.
Труба є потоком байтів. Потік цей має початок (витік) і
кінець (приймач). У витік цього потоку можна записувати дані, а з приймача
— прочитувати. Нитка, яка намагається рахувати дані з порожньої труби,
буде затримана, поки там що-небудь не з'явиться. Навпаки, що пише
нитку може записати в трубу деяку кількість даних, перш ніж труба
заповниться, і подальший запис буде заблокований. На практиці труба
реалізована у вигляді невеликого (декілька кілобайт) кільцевого буфера.
Передавач заповнює цей буфер, поки там є місце. Приймач прочитує
дані, поки буфер не спустіє.
Трубу можна встановити в режим читання і запису без блокування. При цьому
виклики, які в інших умовах були б зупинені і вимушені були
б чекати партнера на іншому кінці труби, повертають помилку з особливим
кодом.
Мабуть, труби є одній з перших реалізацій гармонійно взаємодіючих
процесів по термінології Дейкстри.
Найцікавішою властивістю труби є те, що читання даних з і
запис в неї здійснюється тими ж самими системними викликами read і
write, що і робота із звичайним файлом, зовнішнім пристроєм або мережевим
з'єднанням (сокетом). На цьому заснована техніка перепризначення введення-виводу,
широко використовувана в командних інтерпретаторах UNIX. Вона полягає в тому,
що більшість системних утиліт отримують дані з потоку стандартного
введення (stdin) і видають їх в потік стандартного виводу (stdout). При цьому,
вказуючи як ці потоки термінальний пристрій, файл або трубу, ми можемо
використовувати як введення, відповідно: текст, що набирає з клавіатури,
вміст файлу або стандартне виведення іншої програми. Аналогічно ми
можемо видавати дані відразу на екран, у файл або передавати їх на вхід
іншої програми.
Так, наприклад, компілятор GNU Із складається з трьох основних проходів:
препроцесора, власне компілятора, що генерує текст на асемблері,
і асемблера. При цьому усередині компілятора, насправді, також виконується декілька
проходів по тексту (у описі перераховано вісімнадцять), в основному для
оптимізації, але нас це в даний момент не цікавить, оскільки всі вони
виконуються усередині одного завдання. При цьому всі три завдання об'єднуються
трубами в єдину лінію обробки вхідного тексту — конвеєр (pipeline),
так що проміжні результати компіляції не займають місця на диску.
У системі UNIX труба створюється системним викликом pipe(int flldes;2]) Цей
виклик створює трубу і поміщає дескриптори файлів, відповідні вхідному
і вихідному кінцям труби, в масив fildes. Потім ми можемо ви повнити fork,
в різних процесах перепризначувати відповідні конць труби на місце
stdin і stdout і запустити необхідні програми (приклад 7.7). При цьому
ми отримаємо типовий конвеєр — два завдання, стандартне введення і виведення яких
сполучені трубою.
Приклад 7.7. Код, що створює конвеєр
за допомогою труб
#include <unistd.h>
void pipeline(void){
/* stage 1 */
int pipel[2];
int childl;
int pipe2[2];
int child2;
int child3;
pipe(pipel);
if((childl=fork())==0) {
close(pipel[0]); /* Закрити зайвий кінець труби */
closed); /* Перепризначувати стандартний вивід */
dup(pipel[1]);
close(pipel[1]);
/* Виконати програму */
execlpC'du", "du", "-s", ".", NULL);
/* Ми можемо попасти сюди лише при помилці exec */
perror("Cannot exec");
exit(0);
}
close(pipel [1] );
if (childl==-l) {
perror("Cannot fork");
}
/* stage 2 */
pipe(pipe2);
if ( (child2=fork() )==0) { '. close (0) ; /J" Перепризначувати стандартне
введення */
dup(pipel[0]} ; close (pipel [0] ) ;
•'close (pipe2 [0] ) ; /* Закрити зайвий кінець труби */ close (1) ; /*
Перепризначувати стандартний вивід */
close (pipe2 [1] ) ;
/* Виконати програму */
execlp ("sort", "sort", "-nr", NULL);
/* Ми можемо попасти сюди лише при помилці exec */
perror ("Cannot exec");
exit(O);
}
close (pipel [0] ) ;
close (pipe2 [1] ) ;
if (child2==-l) {
perror ("Cannot fork");
}
/* stage 3 */
if ( (child3=fork() )==0) {
close (0) ; /* Перепризначувати стандартне введення */
dup(pipe2 [0] );
close (pipe2 [0] ) ;
/* Виконати програму */
execlp ("tail", "tail", "-1", NULL) ;
/* Ми можемо попасти сюди лише при помилці exec */
perror ("Cannot exec");
exit (0) ;
}
close (pipe2 [0] ) ;
if (child3==-l) {
perror ("Cannot fork");
}
while (wait (NULL) !=-!) ;
return ;
}
Зрозуміло, що такі труби можна використовувати лише для зв'язку
родинні завдань, тобто таких, які зв'язані відношенням батько-нащадок або є
нащадками одного процесу.
Для зв'язку між неродинними завданнями використовується інший засіб.
іменовані труби (named pipes) в System V і UNIX domain sockets в BSD
UNIX. У різних системах іменовані труби створюються різними систем
ными викликами, але дуже схожі по властивостях, тому стандарт POSIX перед
лагает для створення іменованих труб бібліотечну функцію mkfifc
{з--ls. char * name, mode_t flags); . Ця функція створює
спеціальний файл" Відкриваючи такий файл, програма дістає доступ до одного
з кінців труби Коли дві програми відкриють іменовану трубу, вони
зможуть використовувати її для обміну даними точно так, як і звичайну.
Сучасні системи сімейства Unix надають можливість для одночасної
роботи з декількома трубами (а також з іншими об'єктами, що
описуються дескриптором файлу, — власне файлами, сокетами і т. д.)_,
системний виклик select. Цей виклик повертає список дескрипторів файлів,
які здатні передати або прийняти дані. Якщо жоден з дескрипторів не
готовий до обміну даними, select блокується.
Труби
широко використовуються системами сімейства Unix, і вони внесені до стандарту POSIX.
Ряд операційних систем, що не входять в сімейство Unix, наприклад
VxWorks, також надають цей сервіс.
Поштові скриньки VMS
Система VMS надає засоби, частково аналогічні трубам,
звані поштові скриньки (mailbox). Поштова скринька також є кільцевим
буфером, доступ до якого здійснюється тими ж системними викликами, що
і робота із зовнішнім пристроєм. Системна бібліотека мови VAX З використовує
поштові скриньки для реалізації труб, в основному сумісні з UNIX і стандартом
POSIX. Широко використовуваний сервіс мережевої передачі даних, сокети протоколу
TCP, також дуже схожі на трубу.
Лінки трансп'ютера
У мікропроцесорах сімейства Transputer мікропрограмний реалізовані линки
(link — зв'язок) — синхронний примітив, частково схожий на труби.
Лінки бувають двох типів — фізичні і логічні. Операції над
линками обох типів здійснюються одними і тими ж командами. Фізичний
линк є послідовним інтерфейсом RS432, реалізованим на
кристалі процесора. З линком також асоційовано одне слово пам'яті,
сенс якого буде пояснений далі.
Сучасні трансп'ютери мають чотири фізичних линка. Фізичні линки
можуть передавати дані з швидкістю до 20 Мбіт/с і можуть використовуватися
як для з'єднання трансп'ютерів між собою (мал. 7.7), так і для підключення
зовнішніх пристроїв. Завдяки цьому фізичний линк може використовуватися
як для зв'язку між процесами на різних трансп'ютерах, так і для синхронізації
процесу із зовнішніми подіями і навіть просто для введення-виводу.

Мал. 7.7. Мережа трансп'ютерів, сполучених фізичними
линками
Логічний линк— це просто структура даних, виділена у
фізичному адресному просторі процесора. З точки зору програми, фізичний
і логічний линки нічим не відрізняються, крім того, що описувач фізичного
линка прив'язаний до певної фізичної адреси. Логічний линк
може використовуватися лише для зв'язку між процесами (нагадуємо, що
по прийнятій в трансп'ютері термінології, нитки називаються процесами), що
виконуються на одному трансп'ютері.
Трансп'ютер
Т9000 надає також віртуальні линки— протокол, що дозволяє
двом трансп'ютерам організувати декілька ліній взаємодії через один
фізичний линк, або навіть через ланцюжок маршрутизаторів.
При
передачі даних в линк процес повинен виконати команду out. Ця команда має
три операнди: адреса линка, адреса масиву даних і кількість даних. Для
передачі операндів використовується регістровий стек процесора. Процес, що
виконав таку команду, затримується до тих пір, поки всі дані не
будуть передані (мал. 7.8).

Мал. 7.8. Передача даних через линк
Аналогічно, при прийомі даних з линка, процес повинен
виконати команду in . Ця команда також
має три операнди — адреса линка, адреса буфера, куди необхідно помістити
дані, і розмір буфера. При виконання такої команди процес блокується до
тих пір, поки буфер не буде заповнений даними. При цьому приймач і
передавач можуть використовувати буфери різного розміру, тобто приймач може прочитувати
великий масив даних в декілька прийомів і так далі
Існує також команда alt що дозволяє
процесу чекати дані з декількох линков одночасно. Як одна з
очікуваних подій можна також використовувати сигнал від системного таймера.
Слово, пов'язане з линком, містить покажчик на дескриптор процесу,
чекаючого прийому або передачі даних через линк. Крім того, це слово
може набувати значення NotProcessP вказуюче,
що з'єднання ніхто не чекає. Остання інформація, така, як покажчик
на буфер і розмір буфера, зберігається в дескрипторі процесу.
Напрям передачі даних визначається командою, яку виконає черговий
процес при зверненні до линку. Наприклад, якщо виконується команда out,
призначені для запису дані копіюються в буфер чекаючого процесу.
При цьому покажчик буфера просувається, а лічильник розміру зменшується
на кількість скопійованих даних. Якщо ж в линке записано значення
NotProcessP процес переводиться в стан
чекання і покажчик на його дескриптор поміщається в линк (мал. 7.9).

Мал. 7.9. Алгоритм роботи команд in і out
Аналогічно обробляються запити на читання. Якщо ми маємо більше двох
процесів, що намагаються використовувати один линк, то виникає серйозна
проблема: уважний читач повинен був відмітити, що ми не
сказали, де зберігається інформація про те, чого чекає поточний процес: читання
або записи. Проблема полягає в тому, що ця інформація ніде не зберігається.
Якщо процес спробує записати дані в линк, на якому хтось вже
чекає записи, то дані другого процесу будуть записані поверх даних того,
що чекав. Якщо розміри буферів збіжаться, то чекаючий процес
перебуватиме в переконанні, що він успішно передав всі дані. Тому линки
рекомендується використовувати лише для однонаправленої передачі даних
між двома (не більш!) процесами.
При роботі з фізичним линком дані не копіюються, а передаються або
приймаються через фізичний канал в режимі прямого доступу до пам'яті.
Якщо на іншому кінці линка знаходиться інший трансп'ютер, це все-таки
можна вважати копіюванням, але до линку може бути підключено і якесь
інший пристрій.
В середині 90-х, в епоху розквіту мікропроцесорів цього сімейства,
фірма Inmos поставляла широкий набір трэмов (trem — TRansputer Extension
module) — пристроїв введення-виводу з линком як інтерфейс. Зокрема, поставлялися
трэмы, що дозволяли підключити до трансп'ютера через линк адаптери
Ethernet або SCSI.
Взаємодія із зовнішнім пристроєм через линк дозволяє трансп'ютеру
синхронизовать свою діяльність з цими пристроями без використання
механізму переривань. У [INMOS 72 TRN 203 02] наводиться приклад програмної
імітації векторних переривань з передачею вектора по линку
і мониторным процесом, який приймає ці
вектори з линка і викликає відповідні обробники.
|