Виклик
віддалених процедур (RPC)
Концепція віддаленого виклику процедур
Ідея виклику видалених процедур (Remote Procedure Call - RPC) полягає в розширенні добре відомого і зрозумілого механізму передачі управління і
даних усередині програми, виконується на одній машині, на передачу керування і даних через мережу. Засоби віддаленого виклику процедур призначені для
полегшення організації розподілених обчислень. Найбільша ефективність використання RPC досягається в тих додатках, в яких існує
інтерактивна зв'язок між віддаленими компонентами з невеликим часом відповідей і відносно малою кількістю переданих даних. Такі додатки
називаються RPC-орієнтованими. p>
Характерними рисами виклику локальних процедур є: p>
Асиметричність, то є одна з взаємодіючих сторін є
ініціатором;
Синхронність, тобто виконання викликає процедури при
зупиняється з моменту видачі запиту і відновлюється тільки після
повернення з викликається процедури.
Реалізація віддалених викликів істотно складніше реалізації викликів локальних процедур. Почнемо з того, що оскільки викликає і викликається
процедури виконуються на різних машинах, то вони мають різні адресні простору, і це створює проблеми при передачі параметрів і результатів,
особливо якщо машини не є ідентичними. Так як RPC не може розраховувати на поділювану пам'ять, то це означає, що параметри RPC не повинні містити
покажчиків на клітинки нестековой пам'яті і що значення параметрів повинні копіюватися з одного комп'ютера на інший. Наступним відзнакою RPC від
локального виклику є те, що він обов'язково використає нижележащую систему зв'язку, однак це не повинно бути явно видно ні у визначенні процедур,
ні в самих процедурах. Відстань вносить додаткові проблеми. Виконання що викликає програми і викликається локальної процедури в одній машині
реалізується в рамках єдиного процесу. Але в реалізації RPC беруть участь як мінімум два процеси - по одному в кожній машині. У випадку, якщо одна з них
аварійно завершиться, можуть виникнути наступні ситуації: при аварії викликає процедури віддалено викликані процедури стануть "осиротілими", а при
аварійному завершенні віддалених процедур стануть "знедоленими батьками" викликають процедури, які будуть безрезультатно чекати
відповіді від віддалених процедур. p>
Крім того, існує ряд проблем, пов'язаних з неоднорідністю мов програмування і операційних середовищ: структури даних і структури виклику
процедур, що підтримуються в якому-небудь однією мовою програмування, не підтримуються точно так само у всіх інших мовах. p>
Ці та деякі інші проблеми вирішує широко поширена технологія RPC, що лежить в основі багатьох розподілених операційних систем. p>
Базові операції RPC
Щоб зрозуміти роботу RPC, розглянемо спочатку виконання виклику локальної процедури у звичайній машині, що працює автономно. Хай це, наприклад, буде
системний виклик p>
count = read (fd, buf, nbytes);
де fd - ціле число,
buf
- Масив символів,
nbytes - ціле число. p>
Щоб здійснити виклик, що викликає процедура заштовхує параметри в стек у зворотному порядку (малюнок 3.1). Після того, як виклик read виконаний, він
поміщає повертається значення в регістр, переміщує адреса повернення і повертає керування викликає процедурою, яка обирає параметри з
стека, повертаючи його в початковий стан. Зауважимо, що в мові З параметри можуть бути викликані або за посиланням (by name), або за значенням (by value). За
відношенню до викликається процедурі параметри-значення є ініціалізіруемимі локальними змінними. Її викликає процедура може змінити їх, і це не
вплине на значення оригіналів цих змінних в викликає процедурою. p>
Якщо в спричинюється процедуру передається покажчик на змінну, то зміна значення цієї змінної викликається процедурою призводить до зміни значення цієї
змінної і для викликає процедури. Цей факт дуже істотний для RPC. p>
Існує також інший механізм передачі параметрів, який не використовується в мові С. Він називається call-by-copy/restore і полягає в
необхідності копіювання викликає програмою змінних в стек у вигляді значень, а потім копіювання тому після виконання виклику поверх оригінальних
значень викликає процедури. p>
Рішення про те, який механізм передачі параметрів використовувати, приймається розробниками мови. Іноді це залежить від типу даних для передачі. У мові
С, наприклад, цілі та інші скалярні дані завжди передаються за значенням, а масиви - за посиланням. p>
p>
Рис. 3.1. а) Стек до виконання виклику read;
б) Стек під час виконання процедури;
в) Стек після повернення в зухвалу програму p>
Ідея, покладена в основу RPC, полягає в тому, щоб зробити виклик віддаленої процедури виглядають по можливості також, як і виклик локальної процедури.
Іншими словами - зробити RPC прозорим: викликає процедурою не потрібно знати, що викликається процедура знаходиться на іншій машині, і навпаки. p>
RPC досягає прозорості таким шляхом. Коли викликається процедура дійсно є віддаленою, в бібліотеку поміщається замість локальної
процедури інша версія процедури, яка називається клієнтським стаб (stub - заглушка). Подібно оригінальній процедурою, стаб викликається з використанням
що викликає послідовності (як на малюнку 3.1), так само відбувається переривання при зверненні до ядра. Тільки на відміну від оригінальної процедури він
не поміщає параметри в регістри і не запрошувати у ядра дані, замість цього він формує повідомлення для відправки ядру віддаленої машини. p>
Етапи виконання RPC
Взаємодія програмних компонентів при виконанні віддаленого виклику процедури ілюструється малюнком 3.2. Після того, як клієнтський стаб був
викликаний програмою-клієнтом, його першим завданням є заповнення буфера відправляти повідомлення. У деяких системах клієнтський стаб має
єдиний буфер фіксованої довжини, що заповнюється кожного разу з самого початку при надходженні кожного нового запиту. В інших системах буфер повідомлення
являє собою пул буферів для окремих полів повідомлення, причому деякі з цих буферів вже заповнені. Цей метод особливо підходить для тих випадків,
коли пакет має формат, що складається з великої кількості полів, але значення багатьох з цих полів не змінюються від виклику до виклику. p>
Потім параметри повинні бути перетворені у відповідний формат і вставлені в буфер повідомлення. До цього моменту повідомлення готове до передачі,
тому виконується переривання за викликом ядра. p>
p>
Рис. 3.2. Remote Procedure Call h2>
Коли ядро отримує управління, воно перемикає контексти, зберігає регістри процесора і пам'яті (дескриптори сторінок), встановлює нову
карту пам'яті, яка буде використовуватися для роботи в режимі ядра. Оскільки контексти ядра і користувача розрізняються, ядро має точно скопіювати
повідомлення у свій власний адресний простір, так, щоб мати до нього доступ, запам'ятати адресу призначення (а, можливо, і інші поля заголовка), а
також воно має передати його мережевого інтерфейсу. На цьому завершується робота на стороні клієнта. Чи включається таймер передачі, і ядро може або виконувати
циклічний опитування наявності відповіді, або передати управління планувальником, що вибере будь-який інший процес на виконання. У першому випадку
прискорюється виконання запиту, але відсутній мультипрограмування. p>
На стороні сервера надходять біти поміщаються приймаючої апаратурою або у вбудований буфер, або в оперативну пам'ять. Коли вся інформація буде
отримана, генерується переривання. Оброблювач переривання перевіряє правильність даних пакета і визначає, якому стаб слід їх передати. Якщо жоден з
стаб не очікує цей пакет, обробник повинен або помістити його в буфер, або взагалі відмовитися від нього. Якщо є очікує стаб, то повідомлення
копіюється йому. Нарешті, виконується перемикання контекстів, у результаті чого відновлюються регістри та карта пам'яті, приймаючи ті значення, які вони
мали на момент, коли стаб зробив виклик receive. p>
Тепер починає роботу серверний стаб. Він розпаковує параметри і поміщає їх відповідним чином в стек. Коли все готово, виконується виклик сервера.
Після виконання процедури сервер передає результати клієнтові. Для цього виконуються всі описані вище етапи, тільки у зворотному порядку. p>
Рисунок 3.3 показує послідовність команд, яку необхідно виконати для кожного RPC-виклику, а малюнок 3.4 - яка частка загального часу
виконання RPC витрачається на виконання кожного з описаних 14 етапів. Дослідження були проведені на мультипроцесорної робочої станції DEC Firefly,
і, хоча наявність п'яти процесорів обов'язково вплинуло на результати вимірювань, наведена на малюнку гістограма дає загальне уявлення про процес
виконання RPC. p>
p>
Рис. 3.3. Етапи виконання процедури RPC h2>
p>
Рис. 3.4. Розподіл часу між 14 етапами виконання RPC h2>
1. Виклик стаб b> p>
2. Підготувати буфер b> p>
3. Запакувати параметри b> p>
4. Заповнити поле заголовка b> p>
5. Обчислити контрольну суму в повідомленні b> p>
6. Переривання до ядра b> p>
7. Черга пакету на виконання b> p>
8. Передача повідомлення контролеру по шині QBUS b> p>
9. Час передачі по мережі Ethernet b> p>
10. Отримати пакет від контролера b> p>
11. Процедура обробки переривання b> p>
12. Обчислення контрольної суми b> p>
13. Перемикання контексту в простір користувача b> p>
14. Виконання серверного стаб b> p>
Динамічне зв'язування
Розглянемо питання про те, як клієнт задає місце розташування сервера. Одним з методів вирішення цієї проблеми є безпосереднє використання
мережевої адреси сервера в клієнтської програмі. Недолік такого підходу - його надзвичайна негнучкість: при переміщенні сервера, або при збільшенні числа
серверів, або при зміні інтерфейсу у всіх цих і багатьох інших випадках необхідно перекомпіліровать всі програми, які використовували жорстке
завдання адресу сервера. Для того, щоб уникнути всіх цих проблем, у деяких розподілених системах використовується так зване динамічне зв'язування. p>
Початковим моментом для динамічного зв'язування є формальне визначення (специфікація) сервера. Специфікація містить ім'я файлу-сервера,
номер версії і список процедур-послуг, що надаються даним сервером для клієнтів (малюнок 3.5). Для кожної процедури дається опис її параметрів з
зазначенням того, чи є даний параметр вхідним або вихідним щодо сервера. Деякі параметри можуть бути одночасно вхідними і вихідними --
наприклад, деякий масив, який передається клієнтом на сервер, там модифікується, а потім повертається назад клієнту (операція copy/restore). p>
Рис. 3.5. Специфікація сервера RPC h2>
Формальна специфікація сервера використовується в якості вихідних даних для програми-генератора стаб, яка створює як клієнтські, так і серверні
стаб. Потім вони поміщаються у відповідні бібліотеки. Коли призначена для користувача (клієнтська) програма викликає будь-яку процедуру, визначену
в специфікації сервера, відповідна стаб-процедура зв'язується з двійковим кодом програми. Аналогічно, коли компілюється сервер, з ним зв'язуються
серверні стаб. p>
При запуску сервера найпершим дією є передача свого серверного інтерфейсу спеціальною програмою, званої binder'ом. Цей
процес, відомий як процес реєстрації сервера, включає передачу сервером свого імені, номера версії, унікального ідентифікатора і описувача
місцезнаходження сервера. Описувач системно незалежний і може являти собою IP, Ethernet, X.500 або ще якої-небудь адресу. Крім того, він може містити
та іншу інформацію, наприклад, що відноситься до аутентифікації. p>
Коли клієнт викликає одну з віддалених процедур першого разу, наприклад, read, клієнтський стаб бачить, що він ще не під'єднано до сервера, і посилає
повідомлення binder-програмі з проханням про імпорт інтерфейсу потрібної версії потрібного сервера. Якщо такий сервер існує, то binder передає описувач і
унікальний ідентифікатор клієнтського стаб. p>
Клієнтський стаб при посилці повідомлення із запитом використовує як адресу описувач. У повідомленні містяться параметри і унікальний ідентифікатор,
який ядро сервера використовує для того, щоб направити надійшло повідомлення в потрібний сервер у випадку, якщо їх декілька на цій машині. p>
Цей метод, що полягає в імпорту/експорту інтерфейсів, володіє високою гнучкістю. Наприклад, може бути кілька серверів, що підтримують один і той
ж інтерфейсу, і клієнти розподіляються по серверах випадковим чином. У рамках цього методу стає можливим періодичний опитування серверів, аналіз їх
працездатності і, в разі відмови, автоматичне відключення, що підвищує загальну відмовостійкість системи. Цей метод може також підтримувати
аутентифікацію клієнта. Наприклад, сервер може визначити, що він може бути використаний тільки клієнтами з певного списку. p>
Однак у динамічного зв'язування є недоліки, наприклад, додаткові накладні витрати (тимчасові витрати) на експорт та імпорт
інтерфейсів. Величина цих витрат може бути значною, тому що багато клієнтські процеси існують короткий час, а при кожному старті процесу
процедура імпорту інтерфейсу повинна бути знову виконана. Крім того, у великих розподілених системах може стати вузьким місцем програма binder, а створення
декількох програм аналогічного призначення також збільшує накладні витрати на створення і синхронізацію процесів. p>
Семантика RPC у разі відмов
В ідеалі RPC повинен функціонувати правильно і у разі відмов. Розглянемо наступні класи відмов: p>
Клієнт не може визначити місцезнаходження сервера, наприклад, в
разі відмови потрібного сервера, або через те, що програма клієнта була
скомпільована давно і використала стару версію інтерфейсу сервера. У
цьому випадку у відповідь на запит клієнта надходить повідомлення, що містить код
помилки.
Втрачено запит від клієнта до сервера. Найпростіше рішення - через
певний час повторити запит.
Втрачено відповідь від сервера клієнту. Цей варіант
складніше попереднього, тому що деякі процедури не є
Ідемпотентний. Ідемпотентний називається процедура, запит на виконання
якої можна повторити кілька разів, і результат при цьому не зміниться.
Прикладом такої процедури може служити читання файлу. Але ось процедура
зняття певної суми з банківського рахунку не є Ідемпотентний, і в
випадку втрати відповіді повторний запит може істотно змінити стан
рахунку клієнта. Одним з можливих рішень є приведення всіх
процедур до Ідемпотентний увазі. Однак на практиці це не завжди вдається,
тому може бути використаний інший метод - послідовна нумерація
всіх запитів клієнтським ядром. Ядро сервера запам'ятовує номер самого
останнього запиту від кожного з клієнтів, і при отриманні кожного запиту
виконує аналіз - чи є цей запит первинним або повторним.
Сервер зазнав аварії після отримання запиту. Тут також важливо
властивість Ідемпотентний, але на жаль не може бути застосований підхід з
нумерацією запитів. У даному випадку має значення, коли відбулася відмова
- До чи після виконання операції. Але клієнтське ядро не може розпізнати
ці ситуації, для нього відомо тільки те, що час відповіді закінчився.
Існує три підходи до цієї проблеми:
Чекати до тих пір, поки сервер не перезавантажиться і намагатися
виконати операцію знову. Цей підхід гарантує, що RPC був виконаний до
кінця принаймні один раз, а можливо і більше.
Відразу повідомити додатком про помилку. Цей підхід гарантує, що
RPC був виконаний не більше одного разу.
Третій підхід не гарантує нічого. Коли сервер відмовляє,
клієнтові не надається ніякої підтримки. RPC може бути або не виконаний
взагалі, або виконано багато разів. У всякому випадку цей спосіб дуже легко
реалізувати.
Жоден з цих підходів не є дужепривабливим. А ідеальний варіант, який би гарантував рівно одне виконання RPC, в загальному випадку не
може бути реалізований з принципових міркувань. Нехай, наприклад, віддаленій операцією є друк деякого тексту, яка включає
завантаження буфера принтера та встановлення одного біта в деякому керуючому регістрі принтера, в результаті якої принтер стартує. Аварія сервера може відбутися
як за мікросекунд до, так і за мікросекунд після установки керуючого бита. Момент збою цілком визначає процедуру відновлення, але клієнт про
моменті збою дізнатися не може. Коротше кажучи, можливість аварії сервера радикально змінює природу RPC і ясно відображає різницю між централізованою і
розподіленою системою. У першому випадку крах сервера веде до краху клієнта, та відновлення неможливо. У другому випадку дії з відновлення системи
виконати і можливо, і необхідно. p>
Клієнт зазнав аварії після надсилання запиту. У цьому випадку
виконуються обчислення результатів, яких ніхто не очікує. Такі
обчислення називають "сиротами". Наявність сиріт може викликати
різні проблеми: непродуктивні витрати процесорного часу,
блокування ресурсів, підміна відповіді на поточний запит відповіддю на запит,
який був виданий клієнтської машиною ще до перезапуску системи.
Як надходити з сиротами? Розглянемо 4 можливих рішення. p>
Знищення. До того,
як клієнтський стаб посилає RPC-повідомлення, він робить позначку в журналі,
сповіщаючи про те, що він буде зараз робити. Журнал зберігається на диску або в
інший пам'яті, стійкої до збоїв. Після аварії система перезавантажується,
журнал аналізується і сироти ліквідуються. До недоліків такого підходу
відносяться, по-перше, підвищені витрати, пов'язані із записом про кожного RPC
на диск, а, по-друге, можлива неефективність через появу сиріт
другого покоління, породжених RPC-викликами, виданими сиротами першим
покоління.
Перевтілення. У цьому
випадку всі проблеми вирішуються без використання запису на диск. Метод
полягає в розподілі часу на послідовно пронумеровані періоди.
Коли клієнт перезавантажується, він передає широкомовне повідомлення всім
машин про початок нового періоду. Після прийому цього повідомлення все
віддалені обчислення ліквідуються. Звичайно, якщо мережа сегментована,
то деякі сироти можуть і вціліти.
М'яке перевтілення
аналогічно попередньому випадку, за винятком того, що відшукуються і
знищуються не всі віддалені обчислення, а тільки обчислення
перезавантажуються клієнта.
Закінчення строку. Кожному
запитом відводиться стандартний відрізок часу Т, протягом якого він
повинен бути виконаний. Якщо запит не виконується за відведений час, то
виділяється додатковий квант. Хоча це і вимагає додаткової роботи,
але якщо після аварії клієнта сервер чекає протягом інтервалу Т до
перезавантаження клієнта, то всі сироти обов'язково знищуються.
На практиці жоден з цих підходів не бажаний, більше того, знищення сиріт може погіршити ситуацію. Наприклад, нехай сирота заблокував один або
більше файлів бази даних. Якщо сирота буде раптом знищено, то ці блокування залишаться, крім того знищені сироти можуть залишитися стояти в різних
системних чергах, в майбутньому вони можуть викликати виконання нових процесів і т.п. p>