Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Sam_R_OS.doc
Скачиваний:
2
Добавлен:
19.11.2019
Размер:
5.27 Mб
Скачать

Критична секція

Важливим поняттям синхронізації процесів є поняття "критичнї секції" програми. Критична секція - це частина програми, у якій здійснюється доступ до поділюваних даних. Щоб виключити ефект гонок стосовно деякого ресурсу, необхідно забезпечити, щоб у будь-який момент, у критичній секції, пов’язаній із цим ресурсом, був, максимум, один процес. Цей прийом називають взаємним виключенням.

Найпростіший cnociб забезпечити взаємне виключення - дозволити процесу, що знаходиться в критичній секції, забороняти всі переривання. Однак, цей спосіб - непридатний, оскільки небезпечно довіряти керування системою користувальницькому процесу; він може надовго зайняти процесор, а за умови краху процесу в критичній області, зазнає краху потерпить вся система, тому що переривання не дозволені.

Мал. 2.4. Реалізація критичних секцій з використанням блокуючих перемінних.

Іншим способом є використання блокуючих змінних. Із кожним поділюваним ресурсом пов'язується двійкова перемінна, котра приймає значення 1, якщо ресурс вільний (тобто жоден процес не є в даний момент у критичній секції, пов'язаній з даним процесом), i значення 0, якщо ресурс зайнятий. На малюнку 2.4 зображений фрагмент алгоритму, що використовують для реалізації виключення доступу до поділюваного ресурсу D блокуючої перемінни F(D). Перед входом у критичну секцію процес перевіряє чи вільний ресурс D. Якщо він зайнятий, - то перевірка циклічно повторюється, якщо вільний, - то значення перемінної F(D) встановлюється на 0, i тому процес входить у критичну секцію. Після того, як процес виконає всі дії з поділюваним ресурсом D, значення перемінної F(D) знову встановлюється рівним 1.

Якщо вci процеси написані з використанням вищеописаних угод, то взаємне виключення гарантується. Варто зауважити, що операція перевірки й установки блокуючої перемінної, повинна бути неподільною. Пояснимо це. Нехай у результаті перевірки перемінної процес визначив, що ресурс вільний, але відразу після цього, не встигнувши установити перемінну в значенні 0, був перерваний. Протягом його припинення інший процес зайняв ресурс, ввійшов у свою критичну секцію, але також був перерваний, не завершивши роботу з поділюваним ресурсом. Коли керування було повернуто першому процесу, він, вважаючи ресурс вільним, установив ознаку зайнятості й почав виконувати свою критичну секцію. У такий спосіб був порушений принцип взаємного виключення, що, потенційно, може привести до небажаних наслідків. Щоб уникнути таких ситуацій у системі команд машини, бажано мати єдину команду "перевірка-установка", чи ж реалізовувати системними засобами відповідні програмні примітиви, які б забороняли переривання протягом всієї операції перевірки й установки.

Реалізація критичних секцій з використанням блокуючих перемінних має істотний недолік: протягом часу, коли один процес знаходиться в критичій секції, інший процес, якому потрібен той же ресурс, буде виконувати рутинні дії з опитування блокуючої перемінної, даремно витрачаючи процесорний час. З метою усунення таких ситуацій, може бути використаний так званий апарат подій. За допомогою цього засобу можуть зважуватися не тільки проблеми взаємного виключення, але й більш загальніші задачі синхронізації процесів. У piзниx операційних системах апарат подій реалізується по - своєму, але в будь-якому випадку використовують системні функції аналогічного призначення, що умовно назвемо WAIT(x) i POST(x), де х -ідентифікатор деякої події. На малюнку 2.5 зображений фрагмент алгоритму процесу, що використовує ці функції. Якщо ресурс зайнятий, то процес не виконує циклічне опитування, а викликає системну функцію WAIT(D), тут D позначає подію, що полягає в звільненні ресурсу D. Функція WAІT(D) переводить активний процес у стан ЧЕКАННЯ i робить оцінку в його дескрипторі стосовно того, що процес очікує події D. Процес, що у цей час використовує ресурс D, після виходу з критичної секції виконує системну функцію POST(D), у результаті чого операційна система переглядає чергу процесів, що очікують, i переводить процес, що очікує події D, у стан ГОТОВНІСТЬ.

Узагальнити засіб синхронізації процесів запропонував Дейкстра, що ввів два нових примітиви. В абстрактній формі вони, позначаються Р i V, оперують над цілими ненегативними перемінними, названими семафорами. Нехай S такий семафор. Операції внзначають у такий cпociб:

V(S); - перемінна S збільшується на 1 однією неподільною дією; вибірка, інкрементi запам'ятовування не можуть бути перервані. До S немає доступу іншим процесам під час виконання цієї операції.

P(S); - зменшення S на 1, якщо це можливо. Якщо S=0, то неможливо зменшити S i залишитися в oблacтi цілих негативних значень, у цьому випадку процес, що викпикає Р-операцію, чекає, поки це зменшення стане можливим. Успішна перевірка i зменшення також є неподільною операцією.

Мал. 2.5. Релізація критичної секції, з використанням системних функцій WAIT(D) i POST(D)

В окремому випадку, коли семафор S може приймати тільки значення 0 i 1, він перетворюється в блокуючу перемінну. Операція Р містить у coбi потенційну можливість переходу процесу, що її виконує, у стан чекання, у той час як V-операція може, при деяких обставинах, активізувати інший процес, припинений операцією Р (порівняєте ці операції із системними функціями WAIT i POST).

Розглянемо використання семафорів на класичному прикладі взаємодії двох процесів, що виконуються в режимі мультипрограмування, один із яких дано в буферний пул, a інший зчитує його з буферного пула. Нехай буферний пул складається з N буферів, кожнен з яких може містити один запис. Процес '"письменник" повинен припинятися, коли вci буфери є зайнятими, i активізуватися, при звільненні хоча б одного буфера. Навпроти, процес "читач" припиняється, коли всі буфери порожні i активізується з появою хоча б одного запису.

Введемо два семафори: е - число порожніх буферів i f - число заповнених буферів. Припустимо, запис у буфер i зчитування з буфера з критичними секціями (як у прикладі з принт-сервером на початку даного розділу). Введемо також двоїчний семафор b, що використовують для забезпечення взаємного виключення.

іnt e = N, f = 0, b=1;

void Writer ()

{

while(1)

{

PrepareNexlRecordf); /* пiдготовка нового запису */

P(e); /* Зменшити число вільних буферів, якщо вони є */

/* в іншому випадку - чекати, поки вони звільняться */

P(b); /* Bxiд у критичну секцію */

AddToBuffer(); /* Додати новий запис у буфер */

V(b); /* Вихід iз критичної секції */

V(f); /* Збільшити число зайнятих буферів */

{

{

void Reader ()

{

while(1)

{

P(f); /* Зменшити число зайнятих буферів, якщо вони є */

/* в іншому випадку - чекати, поки вони з'являться

Р(b); /* Bxiд у критичну секцію */

GelFromBufferQ; */ Узяти запис із буфера */

V(b); /* Вихід iз критичної секції */

V(e); /* Збільшити число вільних буферів */

Process Record (); /* Опрацювати запис */

Тупіки

Наведений вище приклад допоможе нам проілюструвати ще одну проблему синхронізації - взаємні блокування, названі також дедлоками (deadlocks), клінчами (clinch) чи тупіками. Якщо переставити місцями операції Р(е) i P(b) у пporpaмi "письменник", то при збігy обставин ці два процеси можуть взаємно заблокувати один одного. Дійсно, нехай "письменник" першим ввійде в критичну секцію i знайде відсутність вільних буферів; він почне чекати, коли "читач" візьме черговий запис із буфера, хоча "читач" не зможе цього зробити, тому що для цього необхідно ввійти в критичну секцію, вхід у яку заблокований процесом "письменником".

Розглянемо ще один приклад тупіка. Нехай двом процесам, що виконуються в режимі мультипрограмування, для виконання їхньої роботи потрібно два ресурси, наприклад, принтер i диск. На малюнку 2.6.(а) показані фрагменти відповідних програм. I нехай після того, як процес А зайняв принтер (установив блокуючу перемінну), він був перерваний. Керування одержав процес У, що спочатку зайняв диск, але при виконанні наступної команди, був заблокований, тому що принтер виявився вже зайнятим процесом А. Керування знову одержав процес А, що у відповідності із своєю програмою, зробив спробу зайняти диск i був заблокований: диск уже зайнятий процесом В. У такому стані процеси А і В можуть знаходитися як завгодно довго.

У залежності від співвідношення швидкостей процесів, вони можуть або зовсім незалежно використовувати поділювані ресурси (г), або утворювати черги до поділюваних pecypciв (в), або ж взаємно блокувати один одного (б). Тупикові ситуації треба відрізняти від простих черг, хоча i ті й інші виникають при спільному використанні pecypciв i ззовні виглядають подібними: процес припиняється і чекає звіпьнення ресурсу. Однак черга - це нормальне явище, невід'ємна ознака високого коефіцієнта використання pecypciв при випадковому надходженні запитів. Вона виникає тоді, коли ресурс недоступний у даний момент. Проте через деякий час він звільняється i процес продовжує своє виконання. Тупік же ж, що видно з його назви, є нерозв'язною ситуацією.

Мал. 2.6. (а) фрагменти програм А і В, що розділяють принтер i диск;

(б) взаємне блокування (клінч); (в) черга до поділюваного диска;

(г) незалежне використання pecypciв.

У розглянутих прикладах тупік був зумовлений двома процесами, але взаємно блокувати один одного можуть i більша кількість процесів.

Проблема тупіків містить у собі наступні задачі:

• запобігання утворення тупиків;

• розпізнавання тупіків;

• відновлення системи після тупіків.

Тупіки можуть бути ліквідовані на стадії написання програм, тобто програми повинні бути написані таким чином, щоб тупік не міг виникнути ні при якому співвідношенні взаємних швидкостей npoцeciв. Так, якби в попередньому прикладі, процеси А й У запитували ресурси в однаковій послідовності, то тупік був би в принципі неможливий. Другий підхід до запобігання тупіків називається динамічним i полягає у дотриманні визначених правил при призначенні ресурсів процесам, наприклад, ресурси можуть виділятися у визначеній послідовності, що є загальною для всіх процесів.

У випадках, коли тупікова ситуація зумовлена багатьма процесами, що використовують багато pecypciв, розпізнавання тупіка є нетривалою задачею. Існують формальні, програмно-реалізовані методи розпізнавання тупіків, засновані на веденні таблиць розподілу pecypciв, а також таблиць запитів до зайнятих pecypciв. Аналіз цих таблиць дозволяє знайти взаємні блокування.

Якщо ж тупікова ситуація все ж таки виникла, то не обов'язково знімати з виконання всі заблоковані процеси. Можна зняти тільки частину з них, при цьому звільняються ресурси, очікувані іншими процесами, можна повернути деякі процеси в область свопінга, можна "відкотити" деякі процеси до так званої контрольної крапки, у якій запам'ятовується вся інформація, необхідна для відновлення виконання програми з даного місця. Контрольні крапки розставляють у пporpaмi в місцях, після яких можливе виникнення тупіка. З усього вищесказаного зрозуміло, що використовувати семафори потрібно дуже обережно, тому що одна незначна помилка може призвести до зупинки системи. Для того, щоб полегшити написания конкретних програм, запропонували високорівневий засіб синхронізації, названий монітором. Moнiтop - це нaбіp процедур, перемінних структур даних. Процеси можуть викликати процедури монітоpa, але не мати доступу до внутрішніх даних монітора. Монітори мають важливу властивість, що робить їx корисними для досягнення взаємного виключення: тільки один процес може бути активним, стосовно монітоpa. Компілятор обробляє виклики процедур монітора особливим образом. Звичайно, коли процес викликає процедуру монітора, то перші кілька інструкцій цієї процедури перевіряють, чи не активний будь-який інший процес, стосовно монітора. Якщо так, то зухвалий процес припиняється, поки інший процес не звільнить монітоp. Таким чином, виключення входу декількох процесів у моніторі реалізується не програмістом, а компілятором, що робить помилки менш ймовірними.

У розподілених системах, що складаються з декількох процесорів, кожний з яких має власну оперативну пам'ять, семафори i монітори виявляються непридатними. У таких системах синхронізація може бути реалізована тільки за допомогою обміну повідомленнями. Докладніше про це - у розділі "Синхронізація в розподілених системах".

Нитки

Багатозадачність є найважливішою властивістю ОС. Для підтримки цієї властивості, ОС визначає і оформляє для себе ті внутрішні одиниці роботи між якими i буде розподілятися процесор та iншi ресурси комп'ютера. Ці внутрішні одиниці роботи в різних ОС мають piзні назви - задача, завдання, процес, нитка. У деяких випадках сутності, що позначаються цими поняттями, принципово відрізняються один від одного.

Згадуючи про процеси, ми відзначали, що операційна система підтримує їхню відособленість: у кожного процесу є свій віртуальний адресний npocтip, кожному процесу призначаються свої ресурси - файли, вікна, семафори i т.д. Така відособленість потрібна для того, щоб захистити один процес від іншого, оскільки вони, спільно використовуючи всі ресурси машини, конкурують один з одним. Взагалі, процеси належать різним користувачам, що поділяють один комп'ютер, i ОС бере на себе роль арбітра в суперечках процесів за ресурси.

За умови мультипрограмування, підвищується пропускна здатність системи, але окремий процес ніколи не виконується швидше, ніж, якби він виконувався в однопрограмному режимі (всякий поділ pecypciв сповільнює роботу одного з учасників, за рахунок додаткових витрат часу на чекання звільнення ресурсу). Однак задача, розв'язувана в рамках одного процесу, може мати внутрішній паралелізм, що упринципі дозволяє прискорити її рішення. Наприклад, у ході виконання задач відбувається звертання до зовнішнього пристрою, i на час цієї операції можна не блокувати виконання процесу, а продовжити обчислення по іншій "галузі" процесу.

Для цих цілей сучасні ОС пропонують використовувати, порівняно, новий механізм багатониткової обробки (multithreading). При цьому вводиться нове поняття "нитки" (thread), а поняття "процес" у значній мipi змінює зміст.

Мультипрограмування тепер реалізується на piвнi ниток i задача, оформлена у вигляді декількох ниток у рамках одного процесу, може бути швидше виконана за рахунок псевдопаралельного (чи рівнобіжного в мультипроцесорній системі) виконання її окремих частин. Наприклад, якщо електронна таблиця була розроблена з урахуванням можливостей багатониткової обробки, то користувач може запросити перерахування свого робочого листа й, одночасно, продовжувати заповнювати таблицю. Особливо ефективно можна використовувати багатонитковість для виконання розподілених додатків, наприклад, багатонитковий сервер може паралельно виконувати запити відразу декількох клієнтів.

Нитки, що належать до одного процесу, не настільки ізольовані одна від одної як процеси в традиційній багатозадачній системі: між ними легко організувати тісну взаємодію. Дійсно, на відміну від процесів, що конкурують з додатками, yci нитки одного процесу завжди належать одному додатку, тому програміст, що пише цей додаток, може заздалегідь продумати роботу безлічі ниток процесу таким чином, щоб вони могли взаємодіяти, а не боротися за ресурси.

У традиційних ОС поняття "нитка" тотожна поняттю "процес". У дійсності часто бажано мати кілька ниток, що виконуються паралельно і розділяють єдиний адресний npocтip, завдяки чому нитки стають подібними до процесів (за винятком поділюваного адресного простору).

Нитки iноді називають полегшеними процесами чи міні-процесами. Дійсно, нитки в багатьох відношення подібні до процесів. Кожна нитка виконується строго і послідовно i має свій власний програмний лічильник та стік. Нитки, як і процеси, можуть, наприклад, породжувати нитки-нащадки, можуть переходити зі стану в стан. Подібно до традиційних процесів (тобто процесам, що складаються з однієї нитки), вони можуть знаходитися в одному з наступних станів: ВИКОНАННЯ, ЧЕКАННЯ i ГОТОВНІСТЬ. Поки одна нитка заблокована, інша нитка того ж процесу може виконуватися. Нитки розділяють процесор так як це роблять процеси, відповідно до різних варіантів планування.

Однак різні нитки в рамках одного процесу не настільки незалежні, як окремі процеси. Уci вони мають той самий адресний простір. Це означає, що вони розділяють ті caмі глобальні перемінні. Оскільки кожна нитка є доступ до кожної віртуальної адреси, то одна нитка може використовувати стек іншої нитки. Між нитками немає повного захисту, тому що, по-перше, це неможливо, а по-друге, не потрібно. Уci нитки одного процесу завжди вирішують загальну задачу одного користувача i апарат ниток використовують для більш швидкого рішення задачі, шляхом її розпаралелювання. При цьому, програмісту дуже важливо мати у своєму розпорядженні зручні засоби організації взаємодії частин oднієї задачі. Kpiм поділу адресного простору, yci нитки розділяють також нaбip відкритих файлів, таймерів, сигналів i т.п.

Отже, нитки мають власні:

  • програмні лічильники;

  • стік;

  • регістри;

  • нитки-нащадки;

  • стани.

Нитки розділяють:

  • адресний пpocтip;

  • глобальні пepeмінні;

  • відкриті файли;

  • таймери;

  • семафори;

  • статистичну інформацію.

Багатониткова обробка підвищує ефективність роботи системи в порівнянні з багатозадачною обробкою. Наприклад, у багатозадачному середовищі Windows може одночасно працювати з електронною таблицею i текстовий редактор. Однак, якщо користувач запитує перерахування свого робочого листа, електронна таблиця блокується доти, поки ця операція не завершиться, що може вимагати значного часу. У багатонитковому середовищі, у випадку, якщо електронна таблиця була розроблена з урахуванням можливостей багатониткової обробки, наданої програмісту, цієї проблеми не виникає i тому користувач завжди має доступ до електронної таблиці.

Широке застосування має багатониткова обробка в розподілених системах. Про це в розділі "Процеси i нитки в розподілених системах".

Деякі приклади задач легше програмувати, використовуючи паралелізм, наприклад, задачі типу "письменник-читач", у яких одна нитка виконує запис у буфер, а інша - зчитує записи з нього. Оскільки вони розділяють загальний буфер, то не потрібно їx робити окремими процесами. Інший приклад використання ниток - це керування сигналами, (наприклад, переривання з клавіатури (del чи break)). Замість обробки сигналу переривання, одна нитка призначається для постійного чекання надходження сигналів. Таким чином, використання ниток може скоротити необхідність у перериваннях користувальницького рівня. У цих прикладах не настільки важливе рівнобіжне виконання, як важлива якість програми.

Hapeштi, у мультипроцесорних системах для ниток з одного адресного простору є можливість виконуватися паралельно на різних процесорах. Це, дійсно, один з головних шляхів реалізації поділу pecypciв у таких системах. 3 іншого боку, правильно сконструйовані програми, що використовують нитки, повинні працювати однаково добре як на однопроцесорній машині в режимі поділу часу між нитками, так i на сучасному мультипроцесорі.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]