Термінова допомога студентам
Дипломи, курсові, реферати, контрольні...

Основные функції і компоненти ядра ОС UNIX

РефератДопомога в написанніДізнатися вартістьмоєї роботи

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

Основные функції і компоненти ядра ОС UNIX (реферат, курсова, диплом, контрольна)

Основные функції і компоненти ядра ОС UNIX.

В цієї маленької частини курсу ми докладніше зупинимося на базових функціях ядра ОС UNIX. Основна мета цієї маленької частини — запровадити слухача курсу (і читача цього документа) в основні ідеї ядра ОС UNIX, тобто. показати, чим керувалися розробники системи під час виборів базових проектних рішень. А не прагнемо викладати технічні деталі організації ядра, оскільки (як це зазвичай буває за будь-яких спроб спільного идейно-технического викладу) ми втратили явне різницю між принциповими і технічними рішеннями.

Возможно, вибір тим цієї маленької частини досить суб'єктивний. Ймовірно, що хтось інший звернув більшої уваги інші питання, пов’язані з функціями ядра ОС. Проте підкреслимо, що ми дотримуємося класичному уявленню про функції ядра ОС, запровадженого ще професором Дейкстрой. Відповідно до цим поданням, ядро будь-який ОС передусім відпо-відає управління основний пам’яттю комп’ютера та віртуальної пам’яттю виконуваних процесів, за управління процесором і планування розподілу процессорных ресурсів між спільно виконуваними процесами, за управління зовнішніми пристроями і, нарешті, забезпечення базових коштів синхронізації і взаємодії процесів. Саме це питання ми розглянемо у цій частини курсу стосовно ОС UNIX (іноді до UNIX взагалі, інколи ж до UNIX System V).

Управління пам’яттю

Основная (чи як його прийнято називати у вітчизняній літератури і документації, оперативна) пам’ять завжди була й залишається досі найбільш критичним ресурсом комп’ютерів. Коли ж врахувати, більшість сучасних комп’ютерів забезпечує 32-разрядную адресацію в користувальних програмах, і всі більшої сили набирає нове покоління 64-разрядных комп’ютерів, стає зрозумілим, що вона практично безнадійно розраховувати, що Крим коли-небудь вдасться оснастити комп’ютери основний пам’яттю такого обсягу, щоб їх вистачило до виконання довільній користувальницької програми, а про забезпеченні мультипрограммного режиму, як у основний пам’яті, власне кажучи, можуть одночасно утримуватися кілька користувальних програм.

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

Операционная система UNIX починала своє існування із застосування дуже простих методів управління пам’яттю (простий своппинг), але у сучасних варіантах системи керувати пам’яттю застосовується дуже витончена техніка.

Віртуальне пам’ять

Идея віртуальної пам’яті далеко ще не нова. Тепер чимало людей вважають, що у основі цієї ідеї лежить необхідність забезпечення (з допомогою ОС) видимості практично необмеженої (32- чи 64-разрядной) адресуемой користувальницької пам’яті за наявності основний пам’яті істотно менших розмірів. Звісно, цьому аспекті дуже важливий. Але і важливо усвідомлювати, що віртуальна пам’ять підтримувалася і комп’ютерах з 16-разрядной адресацією, у яких обсяг основний пам’яті найчастіше істотно перевищував 64 Кбайта.

Вспомните хоча б 16-разрядный комп’ютер PDP-11/70, якого можна було підключити до 2 Мбайт основний пам’яті. Іншим прикладом може бути видатна вітчизняна ЕОМ БЭСМ-6, у якій при 15-разрядной адресації 6-байтовых (48-разрядных) машинних слів обсяг основний пам’яті було доведено до 256 Кбайт. Операційні системи цих комп’ютерів тим щонайменше підтримували віртуальну пам’ять, основним змістом якої було забезпечення захисту користувальних програм однієї одної і надання операційній системі можливості динамічно гнучко перерозподіляти основну пам’ять між одночасно підтримуючими користувальницькими процесами.

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

В найбільш простому і найчастіше використовуваному разі страничной віртуальної пам’яті кожна віртуальна пам’ять (віртуальна пам’ять кожного процесу) фізична основна пам’ять видаються які з наборів блоків чи сторінок однакового розміру. Для зручності реалізації розмір сторінки завжди вибирається рівним числу, що є ступенем 2. Тоді, якщо загальна довжина віртуального адреси є N (останніми роками це теж завжди деяка ступінь 2 — 16, 32, 64), а розмір сторінки є 2M), то віртуальний адресу сприймається як структура, що складається з двох полів: перше полі займає (N-M+1) розрядів адреси — й задає номер сторінки віртуальної пам’яті, друге полі займає (M-1) розрядів і задає усунення всередині сторінки до адресуемого елемента пам’яті (здебільшого — байта). Апаратна інтерпретація віртуального адреси показано малюнку 3.1.

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

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

.

Рис. 3.1. Схема страничной організації віртуальної пам’яті

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

.

Рис. 3.2. Схема сегментно-страничной організації віртуальної пам’яті

Сегментно-страничная організація віртуальної пам’яті дозволяла спільно використовувати одні й самі сегменти даних, і програмного коду в віртуальної пам’яті різних завдань (кожної віртуальної пам’яті існувала окрема таблиця сегментів, але для спільно використовуваних сегментів підтримувалися загальні таблиці сторінок).

В подальшому розгляді ми обмежимося проблемами управління страничной віртуальної пам’яті. З невеликими корективами все обговорювані нижче методи лікування й алгоритми стосуються й сегментной, і сегментно-страничной організаціям.

Как ж досягається можливість наявності віртуальної пам’яті з розміром, істотно перевищують розмір оперативної пам’яті? У елементі таблиці сторінок може бути встановлений спеціальний прапор (що означає відсутність сторінки), наявність змушує апаратуру замість нормального відображення віртуального адреси в фізичний перервати виконання команди, і передати управління відповідному компоненту ОС. Англійський термін «demand paging «(листание на вимогу) досить точно характеризує функції, що їх цим компонентом. Коли програма звертається до віртуальної сторінці, відсутньої в основний пам’яті, тобто. «вимагає «доступу до даних чи програмного коду, операційна система задовольняє ця потреба шляхом виділення сторінки основний пам’яті, переміщення у ній копії сторінки, яка перебуває у зовнішній пам’яті, та відповідній модифікації елемента таблиці сторінок. Після цього відбувається «повернення з переривання », і команди, по «вимозі «якої виконувалися такі дії, продовжує свою виконання.

Наиболее відповідальним дією описаного процесу є виділення сторінки основний пам’яті задоволення вимоги доступу до відсутньої в основний пам’яті віртуальної сторінці. Нагадаємо, що ми розглядаємо ситуацію, коли розмір кожної віртуальної пам’яті може істотно перевершувати розмір основний пам’яті. Це означає, що з виділенні сторінки основний пам’яті запросто знайти не вдасться вільну (не приписанную до будь-якої віртуальної пам’яті) сторінку. І тут операційна система повинна відповідно до закладені у неї критеріями (сукупність цих критеріїв прийнято називати «політикою заміщення », а заснований ними алгоритм заміщення — «алгоритмом підкачування ») знайти деяку зайняту сторінку основний пам’яті, перемістити у разі потреби її вміст на зовнішній пам’ять, належним чином модифікувати відповідний елемент відповідної таблиці сторінок, і після цього продовжити процес задоволення доступу до сторінки.

Существует велику кількість різноманітних алгоритмів підкачування. Обсяг його Демшевського не дозволяє розглянути їх докладно. Відповідний матеріал можна знайти у виданих російською книжках з операційним системам Цикритзиса і Бернстайна, Дейтела і Краков’яка. Проте, аби повернутися до опису конкретних методів управління віртуальної пам’яттю, що застосовуються у ОС UNIX, ми ж наведемо деяку коротку класифікацію алгоритмів підкачування.

Во-первых, алгоритми підкачування діляться на глобальні і локальні. З використанням глобальних алгоритмів операційна система за потреби заміщення шукає сторінку основний пам’яті серед усіх сторінок, незалежно від своїх приналежність до будь-якої віртуальної пам’яті. Локальні алгоритми припускають, що й виникає вимога доступу до відсутньої в основний пам’яті сторінці віртуальної пам’яті ВП1, то сторінка для заміщення буде шукатися лише з боку сторінок основний пам’яті, приписаних до тієї ж віртуальної пам’яті ВП1.

Наиболее поширеними традиційними алгоритмами (як і глобальному, це у локальному варіантах) є алгоритми FIFO (First In First Out) і LRU (Least Recently Used). З використанням алгоритму FIFO для заміщення вибирається сторінка, яка найбільше залишається приписаної до віртуальної пам’яті. Алгоритм LRU передбачає, що заміщати слід ту сторінку, до котрої я найбільше походили звернення. Хоча інтуїтивно здається, що критерій алгоритму LRU є більш правильним, відомі ситуації, у яких алгоритм FIFO працює краще (та, крім цього він значно більше дешево реалізується).

Заметим ще, що час використання глобальних алгоритмів, незалежно від конкретного застосовуваного алгоритму, можливі й теоретично неминучі критичні ситуації, які називаються англійською thrashing (попри велику кількість спроб, хорошого російського еквівалента не вдалося придумати). Розглянемо простий приклад. Нехай за комп’ютером в мультипрограммном режимі виконуються процеси — П1 в віртуальної пам’яті ВП1 і П2 в віртуальної пам’яті ВП2, причому сумарний розмір ВП1 і ВП2 більше розмірів основний пам’яті. Припустимо, зараз часу t1 у процесі П1 виникає вимога віртуальної сторінки ВС1. Операційна система обробляє відповідне переривання і вибирає для заміщення сторінку основний пам’яті С2, приписанную до віртуальної сторінці ВС2 віртуальної пам’яті ВП2 (тобто. в елементі таблиці сторінок, відповідному ВС2, проставляється прапор відсутності сторінки). Для повної обробки вимоги доступу до ВС1 у випадку знадобиться два обміну із зовнішнього пам’яттю (перший, щоб записати поточне вміст С2, другий — щоб прочитати копію ВС1). Оскільки операційна система підтримує мультипрограммный режим роботи, то під час виконання обмінів доступом до процесору отримає процес П2, і він, цілком можливо, вимагатиме доступу зі своєю віртуальної сторінці ВС2 (яку в нього хіба що забрали). Знову буде оброблятися переривання, і ОС може замінити деяку сторінку основний пам’яті С3, яка приписана до віртуальної сторінці ВС3 в ВП1. Коли закінчаться обміни, пов’язані з обробкою вимоги доступу до ВС1, відновиться процес П1, і він, цілком можливо, зажадає доступу зі своєю віртуальної сторінці ВС3 (яку в нього хіба що відібрали). І далі. Загальний ефект у тому, що безупинно працює операційна система, виконуючи незліченні і безглузді обміни із зовнішнього пам’яттю, а користувальні процеси П1 і П2 мало просуваються.

Понятно, що час використання локальних алгоритмів ситуація thrashing, яка зачіпає кілька процесів, неможлива. Однак у принципі, можлива аналогічна ситуація всередині однієї віртуальної пам’яті: ОС може щоразу заміщати ту сторінку, до котрої я процес звернеться в наступний час.

Единственным алгоритмом, теоретично гарантує відсутність thrashing, є так званий «оптимальний алгоритм Биледи «(під назвою котрий придумав його угорського математика). Алгоритм у тому, що з заміщення слід вибирати сторінку, до якої майбутньому найдовше не буде звернень. Зрозуміло, що у динамічної середовищі ОС обізнаність майбутнього неможливо, й у контексті алгоритм Биледи представляє лише теоретичним інтерес (але він успішно застосовується практично, наприклад, в компіляторах для планування використання регістрів).

В 1968 року американський дослідник Пітер Деннинг сформулював принцип локальність посилань (званий принципом Деннинга) і висунув ідею алгоритму підкачування, заснованого на понятті робочого набору. У певному сенсі запропонований їм підхід є практично реалізованої аппроксимацией оптимального алгоритму Биледи. Принцип локальність посилань (недоводжуваний, але який підтверджується практично) у тому, що у період (T-t, T) програма звернулася до сторінкам (С1, С2, …, Сn), то, при належному виборі t запросто цю програму звертатиметься до тих ж сторінкам під час часу (T, T+t). Інакше кажучи, принцип локальність стверджує, що й дуже далеко зазирати у майбутнє, можна добре його прогнозувати з минулого. Набір сторінок (С1, С2, …, Сn) називається робочим набором програми (чи, правильніше, відповідного процесу) в останній момент часу T. Зрозуміло, що з часом робочий набір процесу може змінюватися (як за складом сторінок, і за кількістю). Ідея алгоритму підкачування Деннинга (іноді званого алгоритмом робочих наборів) у тому, що операційна система у кожний час мають забезпечувати його присутність серед основний пам’яті поточних робочих наборів всіх процесів, яким дозволена конкуренція за доступом до процесору. Ми не говорити про технічні деталі алгоритму, а лише зазначимо. По-перше, повна реалізація алгоритму Деннинга практично гарантує відсутність thrashing. По-друге, алгоритм реалізуємо (відома, по меншою мірою, одна його повна реалізація, яка проте зажадала спеціальної апаратної підтримки). По-третє, повна реалізація алгоритму Деннинга викликає дуже серйозні накладні витрати.

Поэтому практично застосовуються полегшені варіанти алгоритмів підкачування, заснованих на виключно ідеї робочого набору. Одне з таких варіантів застосовується й в ОС UNIX (наскільки ми знаємо, переважають у всіх версіях системи, які стосуються галузі System V). Ми коротко опишемо цей варіант в п. 3.1.3.

Аппаратно-независимый рівень управління пам’яттю

Материал, наведений у цьому розділі, хоча й відбиває повному обсязі всі проблеми та розв’язання, пов’язані з міським управлінням віртуальної пам’яттю, достатній у тому, аби збагнути важливість та складність відповідних компонентів ОС. У будь-якій операційній системі управління віртуальної пам’яттю займає центральне місце. Колись Ігор Силін (основний розробник відомої ОС Дубно для БЭСМ-6) висунув теза, відомий у народі як «Теза Силіна »: «Витрати, витрачені управління віртуальної пам’яттю, окупаються ». Гадаю, що кожен фахівець у галузі операційними системами погодиться з істинністю цього тези.

Понятно, як і розробники ОС UNIX приділялася велика увага пошукам і ефективних механізмів управління віртуальної пам’яттю (у сфері операційних систем абсолютно істинним є затвердження, що будь-який хороше рішення зобов’язане бути простим). Однак основний проблемою було те, що UNIX мав бути мобільного операційній системою, легко стерпної на різні апаратні платформи. Хоча на концептуальному рівні всі апаратні механізми підтримки віртуальної пам’яті практично еквівалентні, реальні реалізації часто дуже різняться. Неможливо створити повністю машинно-независимый компонент управління віртуальної пам’яттю. З іншого боку, наявні істотні частини програмного забезпечення, що з управлінням віртуальної пам’яттю, для яких деталі апаратної реалізації не важливі. Однією з досягнень ОС UNIX є грамотне та їхнє ефективне поділ коштів управління віртуальної пам’яттю на аппаратно-независимую і аппаратно-зависимую частини. Коротко розглянемо, як і як вдалося включити в аппаратно-независимую частина підсистеми управління віртуальної пам’яттю ОС UNIX (нижче ми зумисне опускаємо технічні деталі спрощуємо деякі аспекти).

Основная ідея у тому, що ОС UNIX спирається на деяке власне уявлення організації віртуальної пам’яті, яке у аппаратно-независимой частини підсистеми управління віртуальної пам’яттю і пов’язують із конкретної апаратної реалізацією з допомогою аппаратно-зависимой частини. У чому полягає це абстрактне уявлення віртуальної пам’яті?

Во-первых, віртуальна пам’ять кожного процесу представляється як набору сегментів (малюнок 3.3).

.

Рис. 3.3. Сегментна структура віртуального адресного простору

Как це випливає з малюнка, віртуальна пам’ять процесу ОС UNIX розбивається на сегменти п’яти різних типів. Три типу сегментів обов’язкові кожної віртуальної пам’яті, і сегменти цих типів є у віртуальної пам’яті у одному примірнику кожному за типу. Сегмент програмного коду містить лише команди. Реально до нього поміщається відповідний сегмент виконуваного файла, який вказувався як параметра системного виклику exec для цього процесу. Сегмент програмного коду неспроможна модифікуватися під час виконання процесу тому можливо використання одного примірника коду до різних процесів.

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

Сегмент стека — це область віртуальної пам’яті, у якій розміщуються автоматичні перемінні програми, явно чи неявно у ній присутні. Цей сегмент, очевидно, може бути динамічним (тобто. доступними цінами й читання, і з записи), і він, також, має бути приватним (приватний) сегментом процесу.

Разделяемый сегмент віртуальної пам’яті утворюється під час підключенні до неї сегмента поділюваної пам’яті (див. п. 3.4.1). За визначенням, такі сегменти призначені для скоординованого спільного використання кількома процесами. Тому поділюваний сегмент повинен допускати доступ читання і з запису і може розділятися кількома процесами.

Сегменты файлів, відображуваних в віртуальну пам’ять (див. п. 2.4.5), є різновид поділюваних сегментів. Різниця у тому, що й за необхідності звільнити оперативну пам’ять сторінки поділюваних сегментів копіюються («откачиваются ») у спеціальний системну область підкачування (swapping space) на диску, то сторінки сегментів файлів, відображуваних в віртуальну пам’ять, у разі потреби откачиваются безпосередньо в своє місце у області зовнішньої пам’яті, займаній файлом. Такі сегменти також допускають доступ і з читання, і з запису і є потенційно спільно використовуваними.

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

На рівні сторінок підтримується два виду описових структур. Для кожної сторінки фізичної оперативної пам’яті існує описувач, входить у одне із трьох списків. Перший список включає описатели сторінок, неприпустимих модифікації чи відображуваних до області зовнішньої пам’яті будь-якого файла (наприклад, сторінки сегментів програмного коду чи сторінки сегмента файла, відображуваного в віртуальну пам’ять). Для таких сторінок непотрібен простір у сфері підкачування системи; вони або взагалі не вимагають відкачування (переміщення копії на зовнішній пам’ять), або відкачка виробляється у інше місце. Другий список — це список описателей вільних сторінок, тобто. таких сторінок, які підключені до жодної віртуальної пам’яті. Такі сторінки вільні від використання і може залучити до будь-якої віртуальної пам’яті. Нарешті, третій список сторінок включає описатели про анонімних сторінок, тобто. таких сторінок, які можуть змінюватися, але котрим немає «рідного «місця у зовнішній пам’яті.

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

Для кожного сегмента підтримується таблиця відображення, котра зв’язує адреси назв віртуальних сторінок із описателями відповідних фізичних сторінок із першого чи третього списків описателей фізичних сторінок для віртуальних сторінок, присутніх в основний пам’яті, чи з адресами копій сторінок у зовнішній пам’яті для віртуальних сторінок, відсутніх в основний пам’яті. (Точніше сказати, що підтримується окрема таблиця відображення кожному за приватного сегмента і жодна загальна таблиця відображення кожному за яке поділяється сегмента.).

Введение

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

Конечно, внаслідок складність перенесення тієї частини ОС UNIX, що стосується управлінню віртуальної пам’яттю, визначається складністю написання аппаратно-зависимой частини. Чим ближче до архітектура апаратури, підтримує віртуальну пам’ять, до абстрактного моделі віртуальної пам’яті ОС UNIX, тим простіше перенесення. Для справедливості зауважимо, що у переважну більшість сучасних комп’ютерів апаратура виконує функції, істотно перевищують потреби моделі UNIX, отже створення нової аппаратно-зависимой частини підсистеми управління віртуальної пам’яттю ОС UNIX здебільшого перестав бути надмірно складним завданням.

Страничное заміщення основний пам’яті і swapping

Как ми згадували кінці п. 3.1.1, в ОС UNIX використовується певний полегшений варіант алгоритму підкачування, заснований на використанні поняття робочого набору. Основна ідея залежить від оцінці робочого набору процесу з урахуванням використання апаратно (а деяких реалізаціях — програмно) встановлюваних ознак звернення до сторінкам основний пам’яті. (Зауважимо, у цьому підрозділі в описах алгоритму ми розрізняємо функції аппаратно-независимого і аппаратно-зависимого компонентів підсистеми управління віртуальної пам’яттю.).

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

Откачку сторінок, не які входять у робочі набори процесів, виробляє спеціальний системний процесс-stealer. Він починає працювати, коли кількість сторінок на списку вільних сторінок сягає встановленого нижнього порога. Функцією цього процесу є аналіз необхідності відкачування сторінки (на основі ознаки зміни) і запис копії сторінки (якщо це потрібно) в відповідну область зовнішньої пам’яті (тобто. або у системну область підкачування — swapping space для анонімних сторінок, або у певний блок файловою системи для сторінки, що входить у сегмент відображуваного файла).

Очевидно, робочий набір будь-якого процесу може змінюватися під час його виконання. Інакше кажучи, не виключено, коли процес звертається до віртуальної сторінці, відсутньої в основний пам’яті. І тут, звісно ж, виникає апаратне переривання, у результаті якого починає працювати операційна система. Подальший перебіг подій залежить від обставин. Якщо список описателей вільних сторінок не порожній, те з нього вибирається певний описувач, й гарантована відповідна сторінка підключається до віртуальної пам’яті процесу (звісно, після зчитування із зовнішнього пам’яті вмісту копії цієї сторінки, якщо це потрібно).

Но якщо виникає вимога сторінки за умов, коли список описателей вільних сторінок порожній, то починає працювати механізм своппинга. Основний привід до застосування іншого механізму у тому, що просте позбавити сторінки в кожного процесу (включаючи той, який зажадав б сторінку) потенційно вело б до ситуації thrashing, оскільки руйнувало б робочий набір деякого процесу). Будь-який процес, затребовавший сторінку ні з свого поточного робочого набору, стає кандидатом на своппинг. Йому большє нє надаються ресурси процесора, і описувач процесу ставлять у чергу до системного процессу-swapper. Звісно, у цій черги може бути кілька процесів. Процесс-swapper почергово здійснює повний своппинг цих процесів (тобто. відкачування всіх сторінок віртуальної пам’яті, що є в основний пам’яті), поміщаючи відповідні описатели фізичних сторінок на список вільних сторінок, до того часу, коли кількість сторінок на цьому не досягне встановленого у системі верхньої межі. Після закінчення повного своппинга кожного процесу одного з процесів з черги до процессу-swapper надається можливість спробувати продовжити своє виконання (для те, що вільної пам’яті вже вистачити).

Заметим, що ми описали найскладніший алгоритм, якби не пішли який використовували в ОС UNIX. Останній «фактично стандартної «версії ОС UNIX (System V Release 4) використовується понад спрощений алгоритм. Це глобальний алгоритм, у якому ймовірність thrashing погашається з допомогою своппинга. Використовуваний алгоритм називається NRU (Not Recently Used) чи clock. Сенс алгоритму у тому, що процесс-stealer періодично очищає ознаки звернення всіх сторінок основний пам’яті, які входять у віртуальну пам’ять процесів (звідси назва «clock »). Якщо виникає потреба у відкачування (тобто. досягнуть нижню межу розміру списку описателей вільних сторінок), то stealer вибирає кандидатом на відкачування ті сторінки, яких був звернень з записи після останньої «очищення «і яким немає ознаки модифікації (тобто. ті, які можна дешевше звільнити). По-друге чергу вибираються сторінки, які справді потрібно відкачувати. Паралельно працює описане вище алгоритм своппинга, тобто. якщо виникає вимога сторінки, а вільних сторінок немає, то відповідний процес стає кандидатом на своппинг.

В висновок торкнемося одну важливу тему, безпосередньо пов’язану з міським управлінням віртуальної пам’яттю — копіювання сторінок під час спроби записи (copy on write). Як відзначали в п. 2.1.7, і під час системного виклику fork () ОС UNIX утворює процесс-потомок, є повної копією свого предка. Тим щонайменше, у нащадка свою власну віртуальна пам’ять, й ті сегменти, що їх його приватними сегментами, у принципі мала б повністю скопироваться. Проте, як і раніше, приватні сегменти допускають доступ і з читання, і з записи, ОС не знає, було б предок чи нащадок реально виробляти запис у кожну сторінку таких сегментів. Тож було б нерозумно виробляти повне копіювання приватних сегментів під час виконання системного виклику fork ().

Поэтому у разі використовується техніка копіювання сторінок під час спроби записи. Попри те що, що у сегмент запис дозволена, кожної його сторінки встановлюється блокування записи. Тим самим було, під час спроби виконання записи виникає переривання, і ОС з урахуванням аналізу статусу відповідної сегмента приймають рішення виділення нової сторінки, копіюванні її у вмісту оригінальної сторінки і включенні нової сторінки цього разу місце старої в віртуальну пам’ять або процесса-предка, або процесса-потомка (залежно від цього, хто їх намагався писати).

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

Управління процесами і нитками

В операційній системі UNIX традиційно підтримується класична схема мультипрограммирования. Система підтримує можливість паралельного (чи квази-параллельного у разі лише одну апаратного процесора) виконання кількох користувальних програм. Кожному такому виконання відповідає процес ОС. Кожен процес виконується у власному віртуальної пам’яті, і тим самим, процеси захищені одного іншого, тобто. один процес неспроможна неконтроллируемым чином прочитати щось із пам’яті іншого процесу чи записати у ній. Проте контрольовані взаємодії процесів допускаються системою, зокрема з допомогою можливості поділу одного сегмента пам’яті між віртуальної пам’яттю кількох процесів.

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

Ядро системи дає можливості (набір системних викликів) для породження нових процесів, відстежування закінчення породжених процесів тощо. З іншого боку, в ОС UNIX ядро системи — це цілком пасивний набір програм, тож даних. Будь-яка програма ядра може, розпочати працювати лише з ініціативи деякого користувальницького процесу (і під час системного виклику), або через внутрішнього чи зовнішнього переривання (прикладом внутрішнього переривання то, можливо переривання через відсутність у основний пам’яті необхідної сторінки віртуальної пам’яті користувальницького процесу; прикладом зовнішнього переривання є будь-яке переривання процесора з ініціативи зовнішнього устрою). У будь-якому разі вважається, що виконується ядерна частина який звернувся чи перерваного процесу, тобто. ядро працює у тих деякого процесу.

В останні роки у через відкликання значним поширенням про симетричних мультипроцессорных архітектур комп’ютерів (Symmetric Multiprocessor Architectures — SMP) в ОС UNIX упровадили механізм легковажних процесів (light-weight processes), чи ниток, чи потоків управління (threads). Говорячи по-простому, нитку — це процес, выполняющийся в віртуальної пам’яті, використовуваної з іншими нитками тієї самої «великовагового «(тобто. який володіє окремої віртуальної пам’яттю) процесу. У принципі так, легковагі процеси використовувались у операційних системах багато років тому. Вже зрозуміли, що програмування з неконтрольованим використанням спільної па-м'яті приносить більше клопоту і неприємностей, ніж принесуть користі, через необхідності використання явних примітивів синхронізації.

Однако, до нашого часу в практику програмістів не було запроваджено безпечніші методи паралельного програмування, а реальні можливості мультипроцессорных архітектур задля забезпечення розпаралелювання потрібно було якось використовувати. Тому знову на ужиток увійшли легковагі процеси, що тепер дістали назву threads (нитки). Найбільш важливо (з нашого погляду зору) те що впровадження механізму ниток знадобилася істотна переробка ядра. Різні виробники апаратури та програмного забезпечення прагнули якнайшвидше виставити ринку продукт, придатний ефективне використання на SMP-платформах. Тому версії ОС UNIX знову кілька розійшлися.

Все це ми обговоримо докладніше у цьому розділі.

Користувальницька і ядерна складові процесів

Каждому процесу відповідає контекст, коли він виконується. Цей контекст включає вміст користувальницького адресного простору — користувальницький контекст (тобто. вміст сегментів програмного коду, даних, стека, поділюваних сегментів і сегментів файлів, відображуваних в віртуальну пам’ять), вміст апаратних регістрів — регистровый контекст (регістр лічильника команд, регістр стану процесора, регістр покажчика стека і регістри загального призначення), і навіть структури даних ядра (контекст системного рівня), пов’язані з цим процесом. Контекст процесу системного рівня ОС UNIX складається з «статичної «і «динамічних «частин. До кожного процесу маємо одну статична частина контексту системного рівня життя та змінне число динамічних частин.

Статическая частина контексту процесу системного рівня включає таке:

Описувач процесу, тобто. елемент таблиці описателей що у системі процесів. Описувач процесу включає, зокрема, таку інформацію: стан процесу; фізичний адресу в основний чи зовнішньої пам’яті u-области процесу; ідентифікатори користувача, від чийого імені запущено процес; ідентифікатор процесу; іншу інформацію, пов’язану з міським управлінням процесом. U-область (u-area) — індивідуальна кожному за процесу область простору ядра, що має тим властивістю, хоча u-область кожного процесу розташований у окремому місці фізичної пам’яті, u-области всіх процесів мають і той ж віртуальний адресу в адресному просторі ядра. Саме ця означає, що яка б програма ядра не виконувалася, вона завжди виконується, як ядерна частина деякого користувальницького процесу, що саме процесу, u-область якого є «видимої «для ядра в момент часу. U-область процесу містить: покажчик на описувач процесу; ідентифікатори користувача; лічильник часу, протягом якого, процес реально виконувався (тобто. обіймав процесор) як користувача і режимі ядра; параметри системного виклику; результати системного виклику; таблиця дескрипторів відкритих файлів; граничні розміри адресного простору процесу; граничні розміри файла, куди процес може писати;

и т.д.

Динамическая частина контексту процесу — це одне чи кілька стеков, що використовуються процесом за його виконанні як ядра. Кількість ядерних стеков процесу відповідає числу рівнів переривання, підтримуваних конкретної апаратурою.

Принципи організації многопользовательского режиму

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

Исторически ОС UNIX є системою поділу часу, тобто. система має насамперед «справедливо «розділяти ресурси процессора (ов) між процесами, що відносяться до різним користувачам, причому в такий спосіб, щоб час реакції кожного дії інтерактивного користувача перебував у допустимих межах. Однак у останній час зростає тенденція для використання ОС UNIX в додатках реального часу, що і алгоритми планування. Нижче ми опишемо загальну (без технічних деталей) схему планування поділу ресурсів процессора (ов) між процесами в UNIX System V Release 4.

Наиболее поширеним алгоритмом планування в системах поділу часу є кільцевої режим (round robin). Основний зміст алгоритму у тому, що час процесора ділиться на кванти фіксованого розміру, а процеси, готові до виконання, розподіляються на кільцеву чергу. Але ця черги є покажчика — початку й кінця. Коли процес, выполняющийся на процесорі, вичерпує свій квант процесорного часу, він звільняє з процесора, ставлять у кінець черги, а ресурси процесора віддаються процесу, що у початку черги. Якщо выполняющийся на процесорі процес відкладається (наприклад, через обміну з певним зовнішньому пристроєм) доти, як і вичерпає свій квант, то після повторної активізації він працює насамкінець черги (не зміг доопрацювати — не вина системи). Це чудова схема поділу часу у разі, коли всі процеси одночасно вкладаються у оперативної пам’яті.

Однако ОС UNIX завжди була те що, щоб обслуговувати більше процесів, чим можна одночасно в основний пам’яті. Іншими словами, частина процесів, потенційно готових виконуватися, розміщалася у зовнішній пам’яті (куди образ пам’яті процесу потрапляв внаслідок своппинга). Тому була потрібна трохи більше гнучка схема планування поділу ресурсів процессора (ов). У результаті було уведено поняття пріоритету. У ОС UNIX значення пріоритету визначає, по-перше, можливість процесу мати основний пам’яті на рівних конкурувати за процесор. По-друге, від значення пріоритету процесу, власне кажучи, залежить розмір тимчасового кванта, що дається процесу до роботи на процесорі під час досягнення своєї черги. По-третє, значення пріоритету, впливає місце процесу у загальній «черзі процесів до ресурсу процессора (ов).

Схема поділу часу між процесами в пріоритетах у випадку виглядає так. Готові до виконання процеси розподіляються на чергу до процесору у порядку зменшення своїх пріоритетів. Якщо певний процес відпрацював свій квант процесорного часу, та заодно залишився готовим до виконання, він стає у чергу до процесору попереду будь-якого процесу з нижчим пріоритетом, але за будь-яким процесом, які мають тим самим пріоритетом. Якщо певний процес активізується, він також ставлять у чергу за процесом, які мають тим самим пріоритетом. Питання у цьому, коли приймати постанову по своппинге процесу, і коли повертати в оперативну пам’ять процес, вміст пам’яті якого треба було раніше переміщено на зовнішній пам’ять.

Традиционное рішення ОС UNIX полягає у використанні динамічно змінюються пріоритетів. Кожен процес при нашій освіті отримує певний який установлюють системою статичний пріоритет, який надалі можна змінити з допомогою системного виклику nice (див. п. 3.1.3). Цей статичний пріоритет є основою початкового значення динамічного пріоритету процесу, що є реальним критерієм планування. Усі з динамічним пріоритетом не нижче порогового беруть участь у конкуренції за процесор (за схемою, описаної вище). Проте кожен цю раз, коли процес успішно відпрацьовує свій квант на процесорі, його динамічний пріоритет зменшується (величина зменшення залежить від статичного пріоритету процесу). Якщо значення динамічного пріоритету процесу сягає деякого нижньої межі, він працює кандидатом на відкачування (своппинг) і большє нє конкурує за процесор.

Процесс, образ пам’яті якого переміщений на зовнішній пам’ять, також має динамічним пріоритетом. Цей пріоритет це не дає процесу право конкурувати за процесор (що й неможливо, оскільки образ пам’яті процесу перебуває у основний пам’яті), але змінюється, даючи зрештою процесу можливість знову повернутися до основну пам’ять і брати участь у конкуренції за процесор. Правила зміни динамічного пріоритету для процесу, переміщеного у зовнішню пам’ять, у принципі, дуже прості. І чим довше образ процесу перебуває у зовнішній пам’яті, тим паче високий його динамічний пріоритет (конкретне значення динамічного пріоритету, звісно, залежить з його статичного пріоритету). Звісно, раніше чи пізніше значення динамічного пріоритету такого процесу переступить певний поріг, і тоді система приймають рішення про необхідність повернення образу процесу у основну пам’ять. Потому, як і результаті своппинга буде звільнена достатня за величиною область основний пам’яті, процес з пріоритетом, коли вони критичної позначки, буде переміщений в основну пам’ять і буде зацікавлений у відповідності зі своїм пріоритетом конкурувати за процесор.

Как вписуються у цю схему процеси реального часу? Насамперед, потрібно розібратися, що розуміється під концепцією «реального часу «в ОС UNIX. Відомо, що є по крайнього заходу два розуміння терміна — «м'яке реальне час (soft realtime) «і «жорстке реальне час (hard realtime) » .

Жесткое реальне час означає, що кожен подія (внутрішнє чи зовнішнє), що у системі (звернення до ядру системи за деякою послугою, переривання від зовнішнього пристрої і т.д.), має оброблятися системою під час, не перевершували верхньої межі часу, відведеного для таких дій. Режим жорсткого реального часу вимагає завдання чітких тимчасових характеристик процесів, й інші тимчасові характеристики повинні визначати поведінка планувальника розподілу ресурсів процессора (ов) і основний пам’яті.

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

В своїх найостанніших варіантах ОС UNIX підтримує концепцію м’якого реального часу. Це потрібно способом, не які виходять межі основного принципу поділу часу. Як ми відзначали вище, є певний діапазон значень статичних пріоритетів процесів. Певний поддиапазон цього діапазону включає значення статичних пріоритетів процесів реального часу. Процеси, які мають динамічними пріоритетами, заснованими на статичних пріоритетах процесів реального часу, мають такими особливостями:

Кожен з таких процесів надається необмежений згори квант часу на процесорі. Інакше кажучи, котрий зайняв процесор процес реального часу нічого очікувати від нього знято до того часу, поки сам він не заявить про неможливості продовження виконання (наприклад, поставивши обмін з зовнішнім пристроєм). Процес реального часу може бути переміщений з основний пам’яті на зовнішній, коли він готовий до виконання, й у оперативної пам’яті присутній хоча б тільки процес, не належить до категорії процесів реального часу (тобто. процеси реального часу переміщаються на зовнішній пам’ять останніми, причому у порядку спаду своїх динамічних пріоритетів). Будь-який процес реального часу, переміщений на зовнішній пам’ять, але готова до виконання, переноситься знову на основну згадку, як лише у ній утворюється вільна область відповідного розміру. (Вибір процесу реального часу повернення в основну пам’ять виготовляють підставі значень динамічних пріоритетів.)

Тем самим своєрідним, але логічним чином у сучасних варіантах ОС UNIX одночасно реалізована як поділу часу для інтерактивних процесів, і можливість м’якого реального часу для процесів, що з реальним управлінням поведінкою об'єктів у часі.

Традиційний механізм управління процесами лише на рівні користувача

Как властиво операційній системі UNIX взагалі, є дві можливості управління процесами — з допомогою командного мови (одного чи іншого варіанта Shell) і з допомогою мови програмування з безпосереднім використанням системних викликів ядра ОС. Можливості командних мов ми будемо обговорювати в п’яту частину його, а що зосередимося на базових можливостях управління процесами на користувальному рівні, наданих ядром системи.

Прежде всього обрисуємо загальну схему можливостей користувача, що з управлінням процесами. Кожен процес може утворити повністю ідентичний підлеглий процес з допомогою системного виклику fork () і чекати закінчення виконання своїх підлеглих процесів з допомогою системного виклику wait. Кожен процес у будь-який час може цілком змінити вміст свого способу пам’яті з допомогою одній з різновидів системного виклику exec (змінити образ пам’яті відповідно до вмістом зазначеного файла, котрий зберігає образ процесу (виконуваного файла)). Кожен процес може встановити свою власну реакцію на «сигнали », вироблені операційній системи у відповідність до зовнішніми чи внутрішніми подіями. Нарешті, кожен процес може спричинити значення свого статичного (а й викликав цим динамічного) пріоритету з допомогою системного виклику nice.

Для створення нової процесу використовується системний виклик fork. Серед програмування потрібно ставитися до цього системному виклику як до виклику функції, возвращающей ціле значення — ідентифікатор породженого процесу, і потім можна використовувати керувати (в обмеженому сенсі) породжених процесом. Реально, всі системи UNIX, крім початкового, запускаемого при розкрутці системи, утворюються з допомогою системного виклику fork.

Вот що робить ядро системи і під час системного виклику fork:

Виділяє пам’ять під описувач нового процесу у таблиці описателей процесів. Призначає унікальний ідентифікатор процесу (PID) для знову освіченого процесу. Утворює логічний копію процесу, виконує системний виклик fork, включаючи повне копіювання вмісту віртуальної пам’яті процесса-предка в знову створювану віртуальну пам’ять, і навіть копіювання складових ядерного статичного і динамічного контекстів процесса-предка. Збільшує лічильники відкриття файлів (процесс-потомок автоматично успадковує все відкриті файли свого батька). Повертає знову освічений ідентифікатор процесу у точку повернення з системного виклику в процессе-предке і повертає значення 0 в точці повернення процессе-потомке.

Понятно, що створення процесу предок і нащадок починають власним життям, довільним зусиллям змінюючи свій контекст. Зокрема, і предок, і нащадок можуть виконати якійсь із варіантів системного виклику exec (див. нижче), що призводить повного зміни контексту процесу.

Чтобы процесс-предок міг синхронизовать своє виконання з виконанням своїх процессов-потомков, існує системний виклик wait. Виконання цієї системного виклику призводить до припинення виконання процесу до того часу, доки завершиться виконання будь-якого з процесів, що його нащадками. У ролі прямого параметра системного виклику wait вказується адресу пам’яті (покажчик), яким мусить бути повернуто інформація, яка описувала статус завершення чергового процесса-потомка, а відповідним (поворотним) параметром є PID (ідентифікатор процесу) який процесса-потомка.

Сигнал — це спосіб інформування процесу з боку ядра про подію деякого події. Сенс терміна «сигнал «у тому, хоч скільки б однотипних подій у системі цього не сталося, щодо кожній такій групи подій процесу буде подано рівно один сигнал. Тобто. сигнал означає, що обумовлений їм подія сталося, але з несе інформації у тому, скільки саме сталося однотипних подій.

Примерами сигналів (не вичерпними всі можливості) є такі:

закінчення процесса-потомка (через виконання системного виклику exit чи системного виклику signal з параметром «death of child (смерть нащадка) »; виникнення виняткової ситуації у поведінці процесу (вихід за припустимі межі віртуальної пам’яті, спроба запис у область віртуальної пам’яті, яка доступна лише читання тощо.); перевищення верхньої межі системних ресурсів; оповіщення помилки в системних викликах (неіснуючий системний виклик, помилки у параметрах системного виклику, невідповідність системного виклику поточному стану процесу т.д.); сигнали, що посилаються іншим процесом в користувальному режимі (див. нижче); сигнали, вступники внаслідок натискання користувачем певних клавіш на клавіатурі термінала, що з процесом (наприклад, Ctrl-C чи Ctrl-D); сигнали, службовці для трасування процесу.

Для установки реакцію надходження певного сигналу використовується системний виклик.

oldfunction = signal (signum, function),.

где signum — це номер сигналу, на надходження якого встановлюється реакція (все можливі сигнали пронумеровано, кожному номера відповідає власне символічне ім'я; відповідна інформація міститься у документації до кожної конкретної системі UNIX), а function — адресу указываемой користувачем функції, що має бути викликана системою на час вступу зазначеного сигналу даному процесу. Яке значення oldfunction — це адресу функції для реагування на надходження сигналу signum, встановлений попередньому виклик signal. Замість адреси функції у вхідних параметрах можна поставити 1 чи 0. Завдання одиниці призводить до того, що далі для цього процесу надходження сигналу з номером signum ігноруватимуть (це допускається задля всіх сигналів). Якщо ролі значення параметра function зазначений нуль, то після виконання системного виклику signal перше ж надходження даному процесу сигналу з номером signum призведе до завершення процесу (буде проінтерпретовано аналогічно виконання системного виклику exit, див. нижче).

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

kill (pid, signum).

(Этот системний виклик називається «kill », оскільки найчастіше застосовується у тому, щоб примусово закінчити зазначений процес.) Параметр signum задає номер генерованого сигналу (в системному виклик kill можна вказувати в усіх номери сигналів). Параметр pid має такі сенс:

якщо для значення pid зазначено ціле позитивне число, то ядро пошле зазначений сигнал процесу, ідентифікатор якого дорівнює pid; якщо значення pid одно нулю, то зазначений сигнал посилається всім процесам, які належать до тієї ж групі процесів, як і надсилає процес (поняття групи процесів аналогічно поняттю групи користувачів; повний ідентифікатор процесу складається з двох частин — ідентифікатора групи процесів і індивідуального ідентифікатора процесу; до однієї групи автоматично охоплюють усі процеси, мають загального предка; ідентифікатор групи процесу то, можливо змінено з допомогою системного виклику setpgrp); якщо значення pid одно -1, то ядро посилає зазначений сигнал всім процесам, дійсний ідентифікатор користувача яких дорівнює ідентифікатору поточного виконання процесу, що посилає сигнал (див. п. 2.5.1).

Для завершення процесу з щодо його власної ініціативи використовується системний виклик.

exit (status),.

где status — це ціла кількість, яке процессу-предку щодо його інформування про причинах завершення процесса-потомка (як описувалося вище, щоб одержати інформації про статус який процесса-потомка в процессе-предке використовується системний виклик wait). Системний виклик називається exit (тобто. «вихід », оскільки вважається, що кожен користувальницький процес запускається ядром системи (власне, так і є), також завершення користувальницького процесу — це остаточний вихід із нього на ядро.

Системный виклик exit може задаватися явно у будь-якій точці процесу, але, можливо і неявним. У частковості, при програмуванні мовою Сі повернення з функції main призводить до виконання неявно що міститься у програмі системного виклику exit з деяким наперед визначеним статусом. З іншого боку, як вище, можна встановити таку реакцію на які у процес сигнали, коли прихід певного сигналу буде інтерпретуватися як неявний виклик exit. І тут як значення статусу вказується номер сигналу.

Возможности управління процесами, які ми обговорили доти, дозволяють утворити новий процес, є повної копією процесса-предка (системний виклик fork); чекати завершення будь-якого освіченого в такий спосіб процесу (системний виклик wait); породжувати сигнали і реагувати ними (системні виклики kill і signal); завершувати процес з його власної ініціативи (системний виклик exit). Але що залишається незрозумілим, як можна виконати в утвореному процесі довільну програму. Зрозуміло, що у принципі цього можна було б змогу досягти з допомогою системного виклику fork, якщо образ пам’яті процесса-предка заздалегідь побудований отже містить все потенційно необхідні програми. Але, звісно, цей спосіб нераціональне (хоча б бо що призводить до перевитрати пам’яті).

Для виконання довільній програми нинішнього року процесі використовуються системні виклики сімейства exec. Різні варіанти exec злегка різняться способом завдання параметрів. Тут не будемо говорити про деталі (по них краще звертатися до документації по конкретної реалізації). Розглянемо певний узагальнений випадок системного виклику.

exec (filename, argv, argc, envp).

Вот що відбувається за виконанні цього системного виклику. Береться файл безпосередньо з ім'ям filename (то, можливо зазначено повне чи скорочена ім'я файла). Цей файл може бути виконуваних файлом, тобто. являти собою цілковитий образ віртуальної пам’яті. Якщо це насправді так, то ядро ОС UNIX виробляє реорганізацію віртуальної пам’яті процесу, який звернувся до системного виклику exec, знищуючи у ньому старі сеґменти і створюючи нові сегменти, у яких завантажуються відповідні розділи файла filename. Після цього в новоствореному користувальному контексті викликається функція main, якої, як й належить, передаються параметри argv і argc, тобто. певний набір текстових рядків, заданих як параметра системного виклику exec. Через параметр envp оновлений процес може звертатися до змінним свого оточення.

Следует помітити, що з виконанні системного виклику exec не утворюється новий процес, а лише змінюється вміст віртуальної пам’яті існуючого процесу. Інакше кажучи, змінюється лише користувальницький контекст процесу.

Полезные можливості ОС UNIX для спілкування родинних чи незалежно освічених процесів розглядаються нижчий за розділі 3.4.

Поняття нитки (threads)

Понятие «легковагого процесу «(light-weight process), чи, як сьогодні називати їх у сучасних варіантах ОС UNIX, «thread «(нитку, потік управління) давно відомо у сфері операційними системами. Інтуїтивно зрозуміло, що концепції віртуальної пам’яті і потоку команд, выполняющегося в цій віртуальній пам’яті, у принципі, ортогональны. Ні із чого годі було, однією віртуальної пам’яті має відповідати сам і лише одне потік управління. Тому, наприклад, в ОС Multics (розділ 1.1) допускалося (і було прийнятої практикою) мати довільне кількість процесів, виконуваних у спільній (поділюваної) віртуальної пам’яті.

Понятно, що й кілька процесів спільно користуються деякими ресурсами, то, при доступі до цих ресурсів вони мають синхронизоваться (наприклад, з допомогою семафорів, див. п. 3.4.2). Багаторічний досвід програмування з допомогою явних примітивів синхронізації показав, що цей стиль «паралельного «програмування породжує серйозні проблеми під час написання, налагодженні і супроводі програм (найбільш важко виявлені помилки у програмах зазвичай пов’язані з синхронізацією). Це було одній з причин те, що в традиційних варіантах ОС UNIX поняття процесу жорстко пов’язували з поняттям окремої і недоступною й інших процесів віртуальної пам’яті. Кожен процес був захищений ядром операційній системи від неконтрольованого втручання інших процесів. Багато років автори ОС UNIX вважали це однією з основних достоїнств системи (втім, цю думку є сьогодні).

Однако, зв’язування процесу з віртуальної пам’яттю породжує, по крайнього заходу, дві проблеми. Перша проблема пов’язані з так званими системами реального часу. Такі системи, зазвичай, призначені для одночасного управління кількома зовнішніми об'єктами і найприродніше видаються як сукупності «паралельно «(чи «квази-параллельно ») виконуваних потоків команд (тобто. взаємодіючих процесів). Проте, якщо з кожним процесом пов’язана окрема віртуальна пам’ять, то зміна контексту процесора (тобто. його переключення з виконання процесу виконання іншого процесу) є щодо дорогої операцією. Тому традиційний підхід ОС UNIX перешкоджав використанню системи в додатках реального часу.

Второй (і може бути більш істотною) проблемою стала поява про симетричних мультипроцессорных комп’ютерних архітектур (SMP — Symmetric Multiprocessor Architectures). У цих комп’ютерах фізично присутні кілька процесорів, які мають однакові за швидкістю можливості доступу до спільно використовуваної основний пам’яті. Поява подібних машин на світовому ринку, природно, поставило проблему їх ефективне використання. Зрозуміло, що з застосуванні традиційного підходу ОС UNIX до організації процесів від наявності спільної па-м'яті небагато штибу (хоча за наявності можливостей поділюваної пам’яті (див. п. 3.4.1) це можна сперечатися). До появи SMP з’ясувалося, що технологія програмування досі неспроможна запропонувати ефективного й екологічно безпечного способу реального паралельного програмування. Тому довелося повернутися до явному рівнобіжному програмування з допомогою паралельних процесів у спільній віртуальної (а цим, і основний) пам’яті з явною синхронізацією.

Что ж розуміється під «ниткою «(thread)? Це незалежний потік управління, що здійснюється у тих деякого процесу. Фактично, поняття контексту процесу, яку ми обговорювали в п. 3.1.1, змінюється так. Усі, що ні належить до потоку управління (віртуальна пам’ять, дескриптори відкритих файлів тощо.), залишається загалом контексті процесу. Речі, які притаманні потоку управління (регистровый контекст, стеки різного рівня життя та т.д.), переходять із контексту процесу у контекст нитки. Загальна картина показано малюнку 3.4.

.

Рис. 3.4. Співвідношення контексту процесу контекстів ниток

Как це випливає з цього малюнка, всі нитки процесу виконуються у його контексті, але кожна нитку має власний контекст. Контекст нитки, як і контекст процесу, складається з користувальницької та ядерної складових. Користувальницька складова контексту нитки включає індивідуальний стік нитки. Оскільки нитки процесу виконуються у спільній віртуальної пам’яті (всі нитки процесу мають рівних прав доступу до будь-яких частинам віртуальної пам’яті процесу), стік (сегмент стека) будь-який нитки процесу у принципі не захищений від довільного (наприклад, через помилки) доступу із боку інших ниток. Ядерна складова контексту нитки включає її регистровый контекст (зокрема, вміст регістру лічильника команд) і динамічно створювані ядерні стеки.

Приведенное стисле обговорення поняття нитки здається достатнім у тому, аби зрозуміти, що почнеться впровадження в ОС UNIX механізму легковажних процесів вимагає істотних переробок ядра системи. (Завжди важко ввести програму кошти, на підтримку яких вона була спочатку пристосована.).

Підходи до організації ниток й управління ними на різні варіанти ОС UNIX

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

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

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

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

Понятно, що з можливостей реального розпаралелювання функцій оброблювачів залежать загальні тимчасові показники системи. Якщо, наприклад, при проектуванні системи помічено, що типовою картиною є «одночасне «вступ у систему N зовнішніх подій, то бажано гарантувати наявність реальних N пристроїв обробки, у яких можуть базуватися оброблювачі. Цими спостереженнях грунтується підхід компанії Sun Microsystems.

В системі Solaris (правильніше говорити SunOS 4. x, оскільки Solaris в термінології Sun є не операційну систему, а розширену операційну середу) прийнято наступний підхід. Після запуску будь-якого процесу можна зажадати резервування однієї чи кількох процесорів мультипроцессорной системи. Це означає, що операційна система має не надасть жодному іншому процесу можливості виконання на зарезервированном (ых) процессоре (ах). Незалежно від цього, готова до виконання хоча тільки нитку такого процесу, зарезервовані процесори ні використовуватися ні на чого більше.

Далее, при освіті нитки можна довести її правильність за однією або кількома процесорами у складі зарезервованих. Зокрема, отже, у принципі можна прив’язати нитку до певного фіксованому процесору. У випадку деяка сукупність потоків управління прив’язується до деякою сукупності процесорів те щоб середнє час реакції системи реального часу задовольняло зовнішнім критеріям. Вочевидь, що це «ассемблерный «стиль програмування (занадто багато перекладається до користувача), але він відкриває широкі можливості перед розробниками систем реального часу (які, щоправда, після цього залежать тільки від особливостей конкретної ОС, а й від конкретної конфігурації даної комп’ютерної установки). Підхід Solaris переслідує мети задовольнити розробників систем «м'якого «(а, можливо, і «жорсткого ») реального часу, і тому фактично подає до рук кошти розподілу критичних обчислювальних ресурсів.

В інших підходах більшою мірою переслідується мета рівномірної балансування завантаження мультипроцессора. І тут програмісту не надаються кошти явною прив’язки процесорів до процесів чи ниткам. Система допускає явне розпаралелювання не більше загальної віртуальної пам’яті і «обіцяє «, що в міру можливостей все процесори обчислювальної системи буде завантажено рівномірно. Такий підхід забезпечує найефективніший використання загальних обчислювальних ресурсів мультипроцессора, але з гарантує коректність виконання систем реального часу (окрім можливості встановлення спеціальних пріоритетів реального часу, які згадувалися в п. 3.1.2).

Отметим існування ще з однією апаратно-програмної проблеми, що з нитками (але тільки із нею). Проблема пов’язана з тим, що у існуючих симетричних мультипроцессорах зазвичай кожен процесор має власної сверхбыстродействующей буферної пам’яттю (кэшем). Ідея кешу, загалом, у тому, щоб забезпечити процесору дуже швидкий (без необхідності виходу шину доступу до спільної оперативної пам’яті) доступом до найактуальнішим даним. Зокрема, якщо програма виконує запис на згадку про, це дію необов’язково відразу відображається у відповідній елементі основний пам’яті; до певного часу змінений елемент даних можуть утримувати лише у локальному кэше того процесора, у якому виконується програма. Звісно, це суперечить ідеї спільного використання віртуальної пам’яті нитками процесу (і навіть ідеї використання пам’яті, поділюваної між кількома процесами, див. п. 3.4.1).

Это дуже непроста проблема, належить області проблем «когерентності кэшей ». Теоретично є багато підходів до її рішенню (наприклад, апаратне розпізнавання необхідності виштовхування записи з кешу з синхронним оголошенням недійсним змісту всіх кэшей, які включають хоча б елемент даних). Проте за практиці такі складні дії не застосовуються, і звичайним прийомом є скасування режиму кэширования у разі, коли різними процесорах мультипроцессорной системи виконуються нитки процесу чи процеси, використовують поділювану пам’ять.

После запровадження поняття нитки трансформується саме поняття процесу. Тепер краще (і правильніше) розуміти процес ОС UNIX як певний контекст, до складу якого віртуальну пам’ять та інші системні ресурси (включаючи відкриті файли), у якому виконується, по крайнього заходу, один потік управління (нитку), у якого власним (простішим) контекстом. Тепер ядро знає про існування цих двох рівнів контекстів здатне порівняно швидко змінювати контекст нитки (не змінюючи загального контексту процесу) і такий самий, як й раніше, змінювати контекст процесу.

Последнее зауваження належить до синхронізації виконання ниток процесу (точніше було говорити про синхронізації доступу ниток до загальним ресурсів процесу — віртуальної пам’яті, відкритим файлам тощо.). Звісно, можна скористатися (порівняно) традиційними засобами синхронізації (наприклад, семафорами, див. п. 3.4.2). Проте здається, що систему може надати для синхронізації ниток процесу дешевші кошти (бо всі нитки працюють у загальному контексті процесу). Зазвичай це кошти ставляться до класу коштів взаємного винятку (тобто. до класу семафоро-подобных коштів). На жаль, й у ставлення до сьогоденню відсутня будь-яка стандартизація.

Управління вводом/выводом

Мы вже обговорювали проблеми організації ввода/вывода в ОС UNIX в п. 2.6.2. У розділі хочемо розглянути це запитання трохи більше докладно, роз’яснивши деякі технічні деталі. Заодно слід віддавати усвідомлювали, що у будь-якій разі, ми залишаємося на концептуальному рівні. Якщо до вас потрібно написати драйвер деякого зовнішнього устрою для деякого конкретного варіанта ОС UNIX, то муситиме добре читати документацію. Проте знання загальних принципів буде корисно.

Традиционно в ОС UNIX виділяються три типу організації ввода/вывода і, три типу драйверів. Блоковий ввод/вывод переважно призначений до роботи з каталоги і звичайними файлами файловою системи, котрі з базовому рівні мають блочну структуру. У пп. 2.4.5 і 3.1.2 вказувалося, що у користувальному рівні тепер можливо працювати з файлами, прямо відображаючи в сегменти віртуальної пам’яті. Ця можливість сприймається як верхній рівень блокового ввода/вывода. На нижньому рівні блоковий ввод/вывод підтримується блоковими драйверами. Блоковий ввод/вывод, ще, підтримується системної буферизацией (див. п. 3.3.1).

Символьный ввод/вывод служить для прямого (без буферизации) виконання обмінів між адресним простором користувача і відповідатиме пристроєм. Загальною всім символьних драйверів підтримкою ядра є забезпечення функцій пересилки даних між користувальницькими і ядерним адресними просторами.

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

Принципи системної буферизации ввода/вывода

Традиционным способом зниження накладних витрат і під час обмінів з пристроями зовнішньої пам’яті, мають блочну структуру, є буферизация блокового ввода/вывода. Це означає, що кожен блок устрою зовнішньої пам’яті зчитується насамперед у певний буфер області основний пам’яті, званої в ОС UNIX системним кэшем, і вже відтіля в цілому або частково (залежно від виду обміну) копіюється до відповідного користувальницьке простір.

Принципами організації традиційного механізму буферизации є, по-перше, те, що копія вмісту блоку утримується в системному буфері до того часу, доки виникне потреба її заміщення через нестачу буферів (в організацію політики заміщення використовується різновид алгоритму LRU, див. п. 3.1.1). По-друге, і під час записи будь-якого блоку устрою зовнішньої пам’яті реально виконується лише відновлення (або освіта і наповнення) буфера кешу. Справжній обмін з побудовою виконується або за виштовхуванні буфера внаслідок заміщення його вмісту, або за виконанні спеціального системного виклику sync (чи fsync), підтримуваного спеціально для насильницького виштовхування на зовнішній пам’ять оновлених буферів кешу.

Эта традиційна схема буферизации увійшла у в протиріччя з розвиненими у сприйнятті сучасних варіантах ОС UNIX засобами управління віртуальної пам’яттю й у особливості з механізмом відображення файлів в сегменти віртуальної пам’яті (див. пп. 2.4.5 і 3.1.2). (Ми не докладно пояснювати тут суть цих протиріч та запропонуємо читачам поміркувати з цього.) Тож у System V Release 4 з’явилася нова схема буферизации, поки використовувана паралельно з старої схемою.

Суть нової схеми у тому, на рівні ядра фактично відтворюється механізм відображення файлів в сегменти віртуальної пам’яті. По-перше, нагадаємо, про тому, що ядро ОС UNIX справді у власному віртуальної пам’яті. Ця пам’ять має як складну, але принципово ті ж самі структуру, як і користувальницька віртуальна пам’ять. Інакше кажучи, віртуальна пам’ять ядра є сегментно-страничной, і які з віртуальної пам’яттю користувальних процесів підтримується загальної підсистемою управління віртуальної пам’яттю. З цього випливає, по-друге, що практично будь-яка функція, забезпечувана ядром для користувачів, може бути гарантована одними компонентами ядра й інших його компонентів. Зокрема, це стосується і та можливостей відображення файлів в сегменти віртуальної пам’яті.

Новая схема буферизации в ядрі ОС UNIX переважно грунтується, що з організації буферизации годі й робити майже спеціального. Коли одна з користувальних процесів відкриває не відкритий на той час файл, ядро утворює новий сегмент і підключає до цього сегмента що файл. Після цього (незалежно від цього, було б користувальницький процес працювати з файлом у традиційному режимі з допомогою системних викликів read і write чи підключить файл до сегменту своєї віртуальної пам’яті) лише на рівні ядра робота здійснюватиметься про те ядерним сегментом, якого підключений файл на рівні ядра. Основна ідея нового підходу у тому, що усувається розрив управлінням віртуальної пам’яттю і загальносистемної буферизацией (це потрібно було б зробити давно, бо очевидне, що основну буферизацію в операційній системі має продукувати компонент управління віртуальної пам’яттю).

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

Системні виклики керувати вводом/выводом

Для доступу (тобто. щоб одержати можливості наступного операцій ввода/вывода) до файлу будь-якого виду (включаючи спеціальні файли) користувальницький процес має виконати попереднє підключення до файлу з допомогою однієї з системних викликів open, creat, dup чи pipe. Програмні канали відповідні системні виклики ми розглянемо в п. 3.4.3, а поки дещо докладніше, ніж у п. 2.3.3, розглянемо інші «инициализирующие «системні виклики.

Последовательность дій системного виклику open (pathname, mode) наступна:

аналізується несуперечність вхідних параметрів (головним чином, які стосуються прапорам режиму до файлу); виділяється або перебуває простір для описателя файла в системної області даних процесу (u-области); в загальносистемної області виділяється або перебуває існуюче простір розміщувати системного описателя файла (структури file); виробляється пошук в архіві файловій системи об'єкта безпосередньо з ім'ям «pathname «й утворюється чи можна знайти описувач файла рівня файловій системи (vnode в термінах UNIX V System 4); виконується зв’язування vnode з раніше освіченою структурою file.

Системные виклики open і creat (майже) функціонально еквівалентні. Будь-який існуючий файл можна відкрити з допомогою системного виклику creat, і будь-яка новий файл можна створити з допомогою системного виклику open. Проте, стосовно системному виклику creat ми повинні чітко наголосити, у разі свого природного застосування (до створення файла) цей системний виклик створює новий елемент відповідного каталогу (в відповідність до заданим значенням pathname), і навіть створює наразі і відповідним чином инициализирует новий i-узел.

Наконец, системний виклик dup (duplicate — скопіювати) призводить до утворення нового дескриптора вже відкритого файла. Цей специфічний для ОС UNIX системний виклик слугує винятково з метою перенаправлення ввода/вывода (див. п. 2.1.8). Його виконання у тому, що у u-области системного простору користувальницького процесу утворюється новий описувач відкритого файла, у якому знову освічений дескриптор файла (ціла кількість), але посилаючись цього разу вже існуючу общесистемную структуру file і у якому самі самі ознаки і прапори, які відповідають відкритого файлу-образцу.

Другими важливими системними викликами є системні виклики read і write. Системний виклик read виконується так:

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

Операция записи виконується аналогічно, але змінює вміст буфера буферного пулу.

Системный виклик close призводить до того, що драйвер обриває зв’язку з відповідним користувальницьким процесом і (у разі останнього за часом закриття устрою) встановлює общесистемный прапор «драйвер вільний » .

Наконец, для спеціальних файлів підтримується іще одна «спеціальний «системний виклик ioctl. Це єдиний системний виклик, що забезпечується для спеціальних файлів і забезпечується інших різновидів файлів. Фактично, системний виклик ioctl дозволяє довільним чином розширити інтерфейс будь-якого драйвера. Параметри ioctl включають код операції, і покажчик певну область пам’яті користувальницького процесу. Усю інтерпретацію коду операції, і відповідних специфічних параметрів проводить драйвер.

Естественно, що позаяк драйвери переважно призначені керувати зовнішніми пристроями, програмний код драйвера мусить мати відповідні кошти на обробки переривань від устрою. Виклик індивідуальної програми обробки переривань в драйвере відбувається з ядра ОС. У такий спосіб в драйвере можуть оголосити вхід «timeout », якого звертається ядро при закінченні раніше замовленого драйвером часу (такий тимчасової контроль є необхідною при управлінні дуже інтелектуальними пристроями).

Общая схема интерфейсной організації драйверів показано малюнку 3.5. Як свідчить цей малюнок, з погляду інтерфейсів і загальносистемного управління різняться два виду драйверів — символьні і блочні. З погляду внутрішньої організації виділяється ще одна частка драйверів — потокові (stream) драйвери (ми вже згадували про потоках в п. 2.7.1). Проте з свого зовнішнього інтерфейсу потокові драйвери немає від символьних.

.

Рис. 3.5. Інтерфейси і вхідні точки драйверів

Блочні драйвери

Блочные драйвери призначаються обслуговування зовнішніх пристроїв з блокової структурою (магнітних дисків, стрічок тощо.) і вирізняються від інших тим, що вони розробляються та виконуються з допомогою системної буферизации. Інакше кажучи, такі драйвери завжди працюють через системний буферний пул. Як бачимо малюнку 3.5, будь-яке звернення до блоковому драйверу для читання чи записи завжди проходить через попереднє опрацювання, що полягає у спробі знайти копію потрібного блоку в буферном пулі.

В разі, якщо копія необхідного блоку не в буферном пулі або якщо з якоїсь причини потрібно замінити вміст деякого оновленого буфера, ядро ОС UNIX звертається процедури strategy відповідного блокового драйвера. Strategy забезпечує стандартний інтерфейс між ядром і драйвером. З використанням бібліотечних підпрограм, виділені на написання драйверів, процедура strategy може організовувати черги обмінів з побудовою, наприклад, з єдиною метою оптимізації руху магнітних головок на диску. Усі обміни, що їх блоковим драйвером, виконуються з буферної пам’яттю. Перепис потрібної інформацією пам’ять відповідного користувальницького процесу виробляється програмами ядра, завідувачами управлінням буферами.

Символьні драйвери

Символьные драйвери переважно призначені обслуговування пристроїв, обміни із якими виконуються посимвольно, або рядками символів змінного розміру. Типовим прикладом символьного устрою є простою принтер, приймає один символ за обмін.

Символьные драйвери не використовують системну буферизацію. Вони безпосередньо копіюють дані з пам’яті користувальницького процесу і під час операцій записи чи пам’ять користувальницького процесу і під час операцій читання, використовуючи власні буфера.

Следует відзначити, що є можливість забезпечити символьний інтерфейс для блокового устрою. І тут блоковий драйвер використовує додаткових можливостей процедури strategy, дозволяють виконувати обмін не залучаючи системної буферизации. Для драйвера, має блоковим і символьним інтерфейсами, в файлової системі заводиться два спеціальних файла, блоковий і символьний. При кожному зверненні драйвер отримує інформацію про тому, що не режимі його використовують.

Потокові драйвери

Как у п. 2.7.1, основним призначенням механізму потоків (streams) є збільшення рівня модульности і гнучкості драйверів зі складною внутрішньої логікою (найбільше це стосується драйверам, що реалізують розвинені мережні протоколи). Специфікою таких драйверів і те, що більшість програмного коду залежить від особливостей апаратного устрою. Понад те, часто виявляється вигідно по-різному комбінувати частини програмного коду.

Все це призвело до появи потоковой архітектури драйверів, які представляють двунаправленный конвеєр обробних модулів. На початку конвеєра (найближена до користувальницькому процесу) перебуває заголовок потоку, якого передусім надходять звернення з ініціативи користувача. Наприкінці конвеєра (найближена до влаштуванню) перебуває звичайний драйвер устрою. У перервах може розташовуватися довільне число обробних модулів, кожен із яких оформляється відповідно до обов’язковим потоковым інтерфейсом.

Взаємодія процесів

Каждый процес у ОС UNIX виконується у власному віртуальної пам’яті, тобто. а то й робити додаткових зусиль, то навіть процессы-близнецы, освічені у виконання системного виклику fork (), насправді повністю ізольовані одне одного (окрім те, що процесс-потомок успадковує від процесса-предка все відкриті файли). Тим самим було, в ранніх варіантах ОС UNIX підтримувалися дуже слабкі можливості взаємодії процесів, навіть які входять у загальну ієрархію породження (тобто. мають загального предка).

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

По-видимому, застосування такий підхід було реакцією на надмірно складні механізми взаємодії і синхронізації паралельних процесів, які були в історично попередньої UNIX ОС Multics. Нагадаємо (див. розділ 1.1), що у ОС Multics підтримувалася сегментно-страничная організація віртуальної пам’яті, й у загальної віртуальної пам’яті могло виконуватися кілька паралельних процесів, які, природно, могли взаємодіяти через загальну пам’ять. за рахунок можливість включення однієї й тієї ж сегмента в різну віртуальну пам’ять аналогічна можливість взаємодій панувала і для процесів, виконуваних над загальної віртуальної пам’яті.

Для синхронізації таких взаємодій процесів підтримувався загальний механізм семафорів, дозволяє, зокрема, організовувати взаємне виняток процесів в критичних ділянках їх виконання (наприклад, при взаимно-исключающем доступі до поділюваної пам’яті). Цей стиль паралельного програмування у принципі забезпечує більшої гнучкості і ефективність, але фактично є дуже важким від використання. Часто програми з’являються важко виявлені і рідко відтворювані синхронизационные помилки; використання явною синхронізації, не пов’язаної нерозривно з тими об'єктами, доступом до яким синхронизуется, робить логіку програм важко збагненної, а текст програм — важко читаним.

Понятно, що стиль ранніх варіантів ОС UNIX стимулював більш просте програмування. У найпростіших випадках процесс-потомок утворювався лише тим, щоб асинхронно із головною процесом виконати якесь просте дію (наприклад, запис в файл). У складних випадках процеси, пов’язані ієрархією кревності, створювали обробні «конвеєри «з техніки програмних каналів (pipes). Ця техніка особливо рясно застосовується при програмуванні на командних мовами (див. розділ 5.2).

Долгое час батьки-засновники ОС UNIX вважали, що тією області, на яку призначався UNIX (розробка програмного забезпечення, підготовка і супровід технічної документації тощо.) цих можливостей предосить. Проте поступове поширення системи за іншими областях і порівняльна простота нарощування його можливостей призвели до того, що з часом у різні варіанти ОС UNIX разом з’явився явно надлишковий набір системних коштів, виділені на забезпечення можливості взаємодії і синхронізації процесів, які обов’язково пов’язані ставленням кревності (у світі ОС UNIX ці гроші зазвичай називають IPC від Inter-Process Communication Facilities). З появою UNIX System V Release 4.0 (і більше старшої версії 4.2) всі ці кошти було узаконені і увійшли до фактичний стандарт ОС UNIX сучасного зразка.

Нельзя сказати, що державні кошти IPC ОС UNIX ідеальні хоча в якомусь відношенні. Під час розробки складних асинхронних програмних комплексів (наприклад, систем реального часу) найбільше незручностей йде на надмірність коштів IPC. Завжди можливі кілька варіантів реалізації, і часто-густо неможливо знайти критерії вибору. Додаткову проблему створює те що, що у різні варіанти системи кошти IPC реалізуються по-різному, найчастіше одні кошти реалізовані з урахуванням використання інших засобів. Тому ефективність реалізації різниться, що робить ускладнюється розробка мобільних асинхронних програмних комплексів.

Тем щонайменше, знати можливості IPC, безумовно, потрібно, якщо ставитися до ОС UNIX як до серйозного виробничої операційній системі. У розділі ми розглянемо основні стандартизованные можливості у основному для ідейному рівні, не вдаючись у технічні деталі.

Порядок розгляду не відбиває якусь особливу ступінь важливості чи перевагу конкретного кошти. Ми розпочинаємо з пакета коштів IPC, які з’явилися UNIX System V Release 3.0. Цей пакет включає:

кошти, щоб забезпечити можливість наявності спільної процесів пам’яті (сегменти поділюваної пам’яті - shared memory segments); кошти, щоб забезпечити можливість синхронізації процесів при доступі до спільно що використовуються ресурсів, наприклад, до поділюваної пам’яті (семафори — semaphores); кошти, щоб забезпечити можливість посилки процесом повідомлень іншому произвольному процесу (черги повідомлень — message queues).

Эти механізми об'єднують у єдиний пакет, оскільки відповідні системні виклики мають близькими інтерфейсами, а реалізації використовуються багато загальні підпрограми. Ось основні загальні властивості всіх трьох механізмів:

До кожного механізму підтримується загальносистемна таблиця, елементи якої описують всіх у цей час представників механізму (конкретні сегменти, семафори або черги повідомлень). Елемент таблиці містить певний числової ключ, що є обраним користувачем ім'ям представника відповідного механізму. Інакше кажучи, щоб чи більш процесу використовувати певний механізм, вони мають заздалегідь домовитися про іменуванні використовуваного представника цього механізму, і домогтися, щоб він ж, представник не використовувався іншими процесами. Процес, який хотів розпочинати користуватися однією з механізмів, звертається до системи з системним викликом з сімейства «get », прямими параметрами якого є ключ об'єкту і додаткові прапори, а відповідним параметром є числової дескриптор, вживаний у подальших системних викликах аналогічно, як використовується дескриптор файла під час роботи з файлової системою. Допускається використання спеціального значення ключа із символічним ім'ям IPC_PRIVATE, зобов’язуючого систему виділити новий елемент в таблиці відповідного механізму незалежно від наявності або відсутність у ній елемента, що містить те ж значення ключа. При вказуванні інших значень ключа завдання прапора IPC_CREAT призводить до утворення нового елемента таблиці, тоді як таблиці відсутня елемент із зазначеним значенням ключа, чи віднайденню елемента з цим значенням ключа. Комбінація прапорів IPC_CREAT і IPC_EXCL призводить до видачі діагностики про хибну ситуації, тоді як таблиці міститься вже елемент із зазначеним значенням ключа. Захист доступу до раніше створеним елементам таблиці кожного механізму полягає в тих засадах, як і захист доступу до файлам.

Перейдем до більшому вивченню конкретних механізмів цього сімейства.

Розділюваний пам’ять

Для роботи з поділюваної пам’яттю використовуються чотири системних виклику:

shmget створює новий сегмент поділюваної пам’яті чи знаходить існуючий сегмент з тим самим ключем; shmat підключає сегмент із зазначеним дескриптором до віртуальної пам’яті обращающегося процесу; shmdt відключає від віртуальної пам’яті раніше підключений до неї сегмент із зазначеним віртуальним адресою початку; нарешті, системний виклик shmctl служить для управління різноманітними параметрами, пов’язані з існуючим сегментом.

После того, як сегмент поділюваної пам’яті підключено до віртуальної пам’яті процесу, той процес може звертатися до відповідним елементам пам’яті з використанням звичайних машинних команд читання і запис, не вдаючись для використання додаткових системних викликів.

Синтаксис системного виклику shmget виглядає так:

shmid = shmget (key, size, flag);

Параметр size визначає бажаний розмір сегмента в байтах. Далі робота іде за рахунок загальним правилам. Якщо таблиці поділюваної пам’яті перебуває елемент, у якому поставлене ключ, і право доступу не суперечать поточним характеристикам обращающегося процесу, то значенням системного виклику є дескриптор існуючого сегмента (і що звернувся процес не дізнається реального розміру сегмента, хоча згодом таки можна почути з допомогою системного виклику shmctl). Інакше створюється новий сегмент з розміром незгірш від встановленого у системі мінімальної відстані сегмента поділюваної пам’яті і більше встановленого максимальної величини. Створення сегмента значить негайного виділення під нього основною пам’яті. Це відкладається до виконання першого системного виклику підключення сегмента до віртуальної пам’яті деякого процесу. Аналогічно, і під час останнього системного виклику відключення сегмента від віртуальної пам’яті відповідна основна пам’ять звільняється.

Подключение сегмента до віртуальної пам’яті виконується шляхом звернення до системного виклику shmat:

virtaddr = shmat (id, addr, flags);

Здесь id — це раніше отриманий дескриптор сегмента, а addr — бажаний процесом віртуальний адресу, що має відповідати початку сегмента в віртуальної пам’яті. Значенням системного виклику є реальний віртуальний адресу початку сегмента (його значення необов’язково збігається з значенням прямого параметра addr). Якщо значенням addr є нуль, ядро вибирає найзручніший віртуальний адресу початку сегмента. З іншого боку, ядро намагається забезпечити (але з гарантує) вибір такого стартового віртуального адреси сегмента, який би відсутність перекрывающихся віртуальних адрес даного яке поділяється сегмента, сегмента даних, і сегмента стека процесу (останні двоє сегмента можуть розширюватися).

Для відключення сегмента від віртуальної пам’яті використовується системний виклик shmdt:

shmdt (addr);

где addr — це віртуальний адресу початку сегмента в віртуальної пам’яті, раніше отриманий від системного виклику shmat. Природно, система гарантує (з урахуванням використання таблиці сегментів процесу), що цей віртуальний адресу справді є адресою початку (яке поділяється) сегмента в віртуальної пам’яті цього процесу.

Системный виклик shmctl:

shmctl (id, cmd, shsstatbuf);

содержит прямий параметр cmd, ідентифікуючий необхідну конкретне дію, і призначено до виконання різних функцій. Певне, найбільш важлива функція знищення сегмента поділюваної пам’яті. Знищення сегмента виробляється так. Якщо на момент виконання системного виклику жоден процес не підключив сегмент зі своєю віртуальної пам’яті, то основна пам’ять, зайнята сегментом, звільняється, а відповідний елемент таблиці поділюваних сегментів оголошується вільним. Інакше в елементі таблиці сегментів виставляється прапор, який забороняє виконання системного виклику shmget стосовно цього сегмента, але процесам, встигли отримати дескриптор сегмента, як і дозволяється підключати сегмент зі своєю віртуальної пам’яті. За виконання останнього системного виклику відключення сегмента від віртуальної пам’яті операція знищення сегмента завершується.

Семафори

Механизм семафорів, реалізований в ОС UNIX, є узагальненням класичного механізму семафорів загального виду, запропонованого понад 25 тому тому відомим голландським фахівцем професором Дейкстрой. Зауважимо, що доцільність введення такої узагальнення досить сумнівна. Зазвичай навпаки використовувався полегшений варіант семафорів Дейкстры — звані двоичные семафори. Ми не тут заглиблюватись у загальну теорію синхронізації з урахуванням семафорів, але зауважимо, що достатність у випадку двійкових семафорів доведено (відомий алгоритм реалізації семафорів загального виду на основі двійкових). Звісно, аналогічні міркування можна було б застосувати й до варіанту семафорів, застосованому в ОС UNIX.

Семафор в ОС UNIX складається з таких елементів:

значення семафори; ідентифікатор процесу, який хронологічно останнім працювали з семафором; число процесів, очікують збільшення значення семафори; число процесів, очікують нульового значення семафори.

Для роботи з семафорами підтримуються три системних виклику:

semget до створення й отримання доступу до набору семафорів; semop для маніпулювання значеннями семафорів (це той самий системний виклик, що дозволяє процесам синхронизоваться з урахуванням використання семафорів); semctl до виконання різноманітних управляючих операцій над набором семафорів.

Системный виклик semget має наступний синтаксис:

id = semget (key, count, flag);

где прямі параметри key і flag і яке значення системного виклику мають хоча б сенс, що з інших системних викликів сімейства «get », а параметр count задає число семафорів у традиційному наборі семафорів, які мають у тому ж ключем. Після цього індивідуальний семафор ідентифікується дескриптором набору семафорів і номером семафори у тому наборі. Якщо на момент виконання системного виклику semget набір семафорів із зазначеним ключем вже є, то котрий звертається процес отримає відповідний дескриптор, але не дізнається про реальному числі семафорів групи (хоча пізніше це все ж можна почути з допомогою системного виклику semctl).

Основным системним викликом для маніпулювання семафором є semop:

oldval = semop (id, oplist, count);

где id — це раніше отриманий дескриптор групи семафорів, oplist — масив описателей операцій над семафорами групи, а count — розмір цього масиву. Значення, яке системним викликом, є значенням останнього обробленого семафори. Кожен елемент масиву oplist має таку структуру:

номер семафори у зазначеному наборі семафорів; операція; прапори.

Если перевірка прав доступу проходить нормально, і в масиві oplist номери семафорів не за межі загального розміру набору семафорів, то системний виклик виконується так. До кожного елемента масиву oplist значення відповідного семафори змінюється відповідно до значенням поля «операція » .

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

Основным визначенню запровадження масових операцій над семафорами було прагнення вчених дати програмістам можливість уникати тупикових ситуацій у зв’язку з семафорной синхронізацією. Це забезпечується тим, що системний виклик semop, хоч би яким довгим він був (через потенційно необмеженої довжини масиву oplist) виконується, як атомарная операція, тобто. під час виконання semop ні один інший процес неспроможна змінити значення будь-якого семафори.

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

Системный виклик semctl має формат.

semctl (id, number, cmd, arg);

где id — це дескриптор групи семафорів, number — номер семафори групи, cmd — код операції, а arg — покажчик на структуру, вміст якої інтерпретується по-різному, в залежність від операції. Зокрема, з допомогою semctl можна знищити індивідуальний семафор у зазначеній групі. Проте деталі цього системного виклику настільки громіздкі, що ми рекомендуємо у разі потреби звертатися до технічної документації використовуваного варіанта операційній системи.

Черги повідомлень

Для забезпечення можливості обміну повідомленнями між процесами цей механізм підтримується такими системними викликами:

msgget для створення нової черги повідомлень чи отримання дескриптора існуючої черги; msgsnd для посилки повідомлення (вірніше, щодо його постановки в зазначену чергу повідомлень); msgrcv прийому повідомлення (вірніше, для вибірки повідомлення з черги повідомлень); msgctl до виконання низки управляючих дій.

Системный виклик msgget має стандартним для сімейства «get «системних викликів синтаксисом:

msgqid = msgget (key, flag);

Ядро зберігає сполучення вигляді зв’язкового списку (черги), а дескриптор черги повідомлень є індексом в масиві заголовків черг повідомлень. У доповнення до інформації, спільної всіх механізмів IPC в UNIX System V, в заголовку черги зберігаються також:

покажчики першу і останнє повідомлення даної черги; число повідомлень і кількість байтів даних переважають у всіх них разом узятих; ідентифікатори процесів, які останніми послали чи прийняли повідомлення через цю чергу; тимчасові мітки останніх виконаних операцій msgsnd, msgrsv і msgctl.

Как зазвичай, і під час системного виклику msgget ядро ОС UNIX або створює нову чергу повідомлень, вписуючи її заголовок в таблицю черг повідомлень і повертаючи користувачеві дескриптор новоствореної черги, або знаходить елемент таблиці черг повідомлень, у якому зазначений ключ, і повертає відповідний дескриптор черги. На малюнку 3.6 показані структури даних, використовувані в організацію черг повідомлень.

.

Рис. 3.6. Структури даних, використовувані в організацію черг повідомлень

Для посилки повідомлення використовується системний виклик msgsnd:

msgsnd (msgqid, msg, count, flag);

где msg — це покажчик на структуру, що містить визначається користувачем целочисленный тип повідомлення й символьний масив — власне повідомлення; count задає розмір сполучення байтах, а flag визначає дії ядра коли за межі допустимих розмірів внутрішньої буферної пам’яті.

Для здобуття права ядро успішно поставило вказане повідомлення зазначену чергу повідомлень, потрібно виконати такі умови: котрий звертається процес має мати відповідні права за записом у цю чергу повідомлень; довжина повідомлення має перевершувати встановлений системі верхня межа; загальна довжина повідомлень (включаючи знову посылаемое) має перевершувати встановлений межа; вказаний у повідомленні тип повідомлення може бути позитивним цілим числом. І тут що звернувся процес успішно продовжує свою виконання, залишивши відправлений повідомлення буфері черги повідомлень. Тоді ядро активізує (пробуджує) всі, що чекає надходження повідомлень з цієї черги.

Если ж виявляється, що новий повідомлення неможливо котушка в ядрі через перевищення верхньої межі сумарною довжини повідомлень, що у одній черзі повідомлень, то що звернувся процес відкладається (усыпляется) до того часу, поки чергу повідомлень не разгрузится процесами, потребують отримання повідомлень. Щоб уникнути такого відкладання, котрий звертається процес має вказати й у числі параметрів системного виклику msgsnd значення прапора з символічним ім'ям IPC_NOWAIT (як у використання семафорів), щоб ядро видало який свідчить про помилку код повернення системного виклику mgdsng в у разі неможливості включити повідомлення зазначену чергу.

Для прийому повідомлення використовується системний виклик msgrcv:

count = msgrcv (id, msg, maxcount, type, flag);

Здесь msg — це покажчик на структуру даних в адресному просторі користувача, призначену розміщувати прийнятого повідомлення; maxcount задає розмір області даних (масиву байтів) у структурі msg; значення type специфицирует тип повідомлення, яке бажано прийняти; значення параметра flag вказує ядру, що слід зробити, тоді як зазначеної черги повідомлень відсутня повідомлення із зазначеним типом. Яке значення системного виклику задає реальна кількість байтів, переданих користувачеві.

Выполнение системного виклику, звісно ж, починається з перевірки правомочності доступу обращающегося процесу зазначеної черги. Далі, якщо значенням параметра type є нуль, ядро вибирає перше повідомлення з зазначеної черги повідомлень і копіює їх у задану користувальницьку структуру даних. Після цього коригується інформація, у заголовку черги (число повідомлень, сумарний величину і т.д.). Якщо будь-які процеси було через переповнення черги повідомлень, усі вони активізуються. Що стосується, якщо значення параметра maxcount виявляється менше реального розміру повідомлення, ядро не видаляє повідомлення з черзі й повертає код помилки. Проте, якщо заданий прапор MSG_NOERROR, то вибірка повідомлення виробляється, й у буфер користувача переписуються перші maxcount байтів повідомлення.

Путем завдання відповідного значення параметра type користувальницький процес вимагатиме вибірки повідомлення деякого конкретного типу. Якщо це значення позитивного бачиться цілим числом, ядро вибирає з черги повідомлень перше повідомлення з такою самою типом. Якщо ж значення параметра type є негативне ціла кількість, то ядро вибирає з черги перше повідомлення, значення типу якого менше, або одно абсолютному значенням параметра type.

Во першій-ліпшій нагоді, тоді як зазначеної черги відсутні повідомлення, відповідні специфікації параметра type, ядро відкладає (присипляє) що звернувся процес до появи у черги необхідного повідомлення. Проте, тоді як параметрі flag поставлено значення прапора IPC_NOWAIT, то процес негайно оповіщається про відсутність сполучення черги шляхом повернення коду помилки.

Системный виклик.

msgctl (id, cmd, mstatbuf);

служит для опитування стану описателя черги повідомлень, зміни його зі стану (наприклад, зміни прав доступу до черги) й у знищення зазначеної черги повідомлень (деталі ми опускаємо).

Програмні канали

Как ми можемо вже неодноразово відзначали, традиційним засобом взаємодії і синхронізації процесів в ОС UNIX є програмні канали (pipes). Теоретично програмний канал дозволяє взаємодіяти кожному числу процесів, забезпечуючи дисципліну FIFO (first-in-first-out). Інакше кажучи, процес, читає з програмного каналу, прочитає ті дані, хто був записані в програмний канал найбільш давно. У традиційної реалізації програмних каналів для зберігання даних використовувалися файли. У середовищі сучасних версіях ОС UNIX для реалізації програмних каналів застосовуються інші засоби IPC (зокрема, черги повідомлень).

Различаются два виду програмних каналів — іменовані і неіменовані. Іменований програмний канал може бути для спілкування, і синхронізації довільних процесів, знають ім'я даного програмного каналу та мають відповідні права доступу. Неименованным програмним каналом можуть користуватися тільки створив його процес та її нащадки (необов'язково прямі).

Для створення іменованого програмного каналу (чи одержання щодо нього доступу) використовується звичайний файловий системний виклик open. До сформування ж неіменованого програмного каналу існує спеціальний системний виклик pipe (історично ближчий). Проте після отримання відповідних дескрипторів обидва виду програмних каналів використовуються однаково з допомогою стандартних файлових системних викликів read, write і close.

Системный виклик pipe має наступний синтаксис:

pipe (fdptr);

где fdptr — це покажчик на масив з цих двох цілих чисел, куди після створення неіменованого програмного каналу будуть можна побачити дескриптори, призначені для читання з програмного каналу (з допомогою системного виклику read) і запис у програмний канал (з допомогою системного виклику write). Дескриптори неіменованого програмного каналу — це звичайні дескриптори файлів, тобто. такому програмному каналу відповідають два елемента таблиці відкритих файлів процесу. Тому, за наступному використанні системних викликів read і write процес не зобов’язаний відрізняти випадок використання програмних каналів від нагоди використання звичайних файлів (власне, цьому й заснована ідея перенаправлення ввода/вывода та молодіжні організації конвеєрів).

Для створення поіменованих програмних каналів (одержання доступу до існуючих каналам) використовується звичайний системний виклик open. Основною відмінністю пива від нагоди відкриття звичайного файла і те, що й іменований програмний канал відкривається на запис, і тут жоден процес не відкрив хоча б програмний канал для читання, то котрий звертається процес блокується (усыпляется) до того часу, поки певний процес не відкриє даний програмний канал для читання (аналогічно обробляється відкриття для читання). Привід від використання такого режиму праці полягає у тому, що, власне кажучи, безглуздо давати доступом до програмному каналу для читання (запис) до того часу, поки певний інший процес не знайде готовності писати у цей програмний канал (відповідно читати потім із нього). Зрозуміло, якби цю схему вона була абсолютною, то один процес не міг би почати працювати з заданим именованным програмним каналом (хтось має бути першим). Тож у числі прапорів системного виклику open є прапор NO_DELAY, завдання якого призводить до того, що іменований програмний канал відкривається незалежно від наявності партнера.

Запись даних в програмний канал і читання даних із програмного каналу (незалежно від цього, іменований він чи неименованный) виконуються з допомогою системних викликів read і write. Відмінність від нагоди використання звичайних файлів лише у цьому, що з записи дані вкладаються у початок каналу, а під час читання вибираються (звільняючи відповідну область пам’яті) з кінця каналу. Як завжди, можливі ситуації, коли за спробі запис у програмний канал виявляється, що канал переповнений, і тоді котрий звертається процес блокується, поки канал не разгрузится (якщо тільки зазначений прапор небажаності блокування серед параметрів системного виклику write), чи коли за спробі читання з програмного каналу виявляється, що канал порожній (чи ньому відсутня необхідну кількість байтів інформації), і тоді котрий звертається процес блокується, поки канал не загрузиться відповідним чином (за умови що не зазначений прапор небажаності блокування серед параметрів системного виклику read).

Окончание роботи процесу з належним програмним каналом (незалежно від цього, іменований він чи неименованный) проводиться за допомогою системного виклику close. У основному, дії ядра при закритті програмного каналу аналогічні діям при закритті звичайного файла. Проте є відмінність у тому, що з виконанні останнього закриття каналу за записом всі, що чекає читання з програмного каналу (тобто. процеси, які звернулися до ядру з системним викликом read і відкладені через брак даних в каналі), активізуються з поверненням коду помилки з системного виклику. (Це аж ніяк виправдано у разі неименованных програмних каналів: якщо достеменно відомо, що більше нічого читати, навіщо змушувати далі чекати читання. Для поіменованих програмних каналів це рішення перестав бути очевидним, але чи відповідає загальної політиці ОС UNIX про ранньому попередженні процесів.).

Програмні гнізда (sockets)

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

Эти базові можливості був у принципі достатніми до створення мережевих утиліт; зокрема, з їхньої основі створили вихідний в ОС UNIX механізм мережевих взаємодій uucp. Проте організація мережевих взаємодій користувальних процесів була скрутна здебільшого тому, що час використання конкретної мережевий апаратури і конкретного мережного протоколу вимагалося виконувати безліч системних викликів ioctl, що робив програми залежать від специфічної мережевий середовища. Був потрібний підтримуваний ядром механізм, дозволяє приховати особливості цього середовища й дозволити однаково взаємодіяти процесам, выполняющимся однією комп’ютері, межах однієї локальної сіті або рознесеним на різні комп’ютери територіально розподіленої мережі. Перше розв’язання проблеми запропонували і реалізовано UNIX BSD 4.1 в 1982 р. (ввідну інформацію див. в п. 2.7.3).

На рівні ядра механізм програмних гнізд підтримується трьома складовими: компонентом рівня програмних гнізд (незалежних від мережного протоколу, й середовища передачі), компонентом протокольного рівня (незалежних середовища передачі) і компонентом управління мережним пристроєм (див. малюнок 3.7).

.

Рис. 3.7. Один із можливих конфігурацій програмних гнізд

Допустимые комбінації протоколів і драйверів задаються при конфігурації системи, і під час роботи системи їх змінювати не можна. Легко бачити, що у духом організація програмних гнізд близька до ідеї потоків (див. пп. 2.7.1 і 3.4.6), оскільки полягає в поділі функцій фізичного управління пристроєм, протокольних функцій і державних функцій інтерфейсу з користувачами. Але це менш гнучка схема, оскільки допускає зміни конфігурації «на ходу » .

Взаимодействие процесів з урахуванням програмних гнізд грунтується на моделі «клієнт-сервер ». Процесс-сервер «слухає (listens) «своє програмне гніздо, жодну з кінцевих точок двунаправленного шляху комунікацій, а процесс-клиент намагається спілкуватися із процессом-сервером через інше програмне гніздо, що є другий кінцевою точкою якого комунікаційного шляху й, можливо, розміщеного іншою комп’ютері. Ядро підтримує внутрішні з'єднання та маршрутизацію даних від клієнта до сервера.

Программные гнізда зі спільними комунікаційними властивостями, такі як спосіб іменування і протокольний формат адреси, групуються в домени. Найчастіше використовуваними є «домен системи UNIX «для процесів, які взаємодіють через програмні гнізда у межах комп’ютера, і «домен Internet «для процесів, які взаємодіють у мережі відповідно до сімейством протоколів TCP/IP (див. п. 2.7.2).

Выделяются два типу програмних гнізд — гнізда з віртуальним з'єднанням (у початковій термінології stream sockets) і датаграммные гнізда (datagram sockets). З використанням програмних гнізд з віртуальним з'єднанням забезпечується передача даних від клієнта до сервера як безперервного потоку байтів щодо гарантії доставки. У цьому на початок передачі має бути встановлено з'єднання, підтримана остаточно комунікаційної сесії. Датаграммные програмні гнізда не гарантують абсолютної надійної, послідовної доставки повідомлень і відсутності такої дублікатів пакетів даних — датаграмм. Для використання датаграммного режиму непотрібен попереднє дороге встановлення сполук, і тому цей режим у часто є кращим. Система за умовчанням сама забезпечує підходящий протокол кожної припустимою комбінації «домен-гнездо ». Наприклад, протокол TCP використовують за вмовчанням для віртуальних сполук, а протокол UDP — для датаграммного способу комунікацій (інформація про ці протоколах представленій у п. 2.7.2).

Для роботи з програмними гніздами підтримується набір спеціальних бібліотечних функцій (в UNIX BSD це системні виклики, проте, як ми відзначали в п. 2.7.3, в UNIX System V вони реалізовані з урахуванням потокового інтерфейсу TLI). Розглянемо коротко інтерфейси і семантику цих функцій.

Для створення нової програмного гнізда використовується функція socket:

sd = socket (domain, type, protocol);

где значення параметра domain визначає домен даного гнізда, параметр type вказує тип створюваного програмного гнізда (з віртуальним з'єднанням чи датаграммное), а значення параметра protocol визначає бажаний мережевий протокол. Зауважимо, що й значенням параметра protocol є нуль, то система сама вибирає підходящий протокол для комбінації значень параметрів domain і type, це найпоширеніший спосіб використання функції socket. Яке функцією значення є дескриптором програмного гнізда і використовується в усіх подальших функціях. Виклик функції close (sd) призводить до закриттю (знищення) зазначеного програмного гнізда.

Для зв’язування раніше створеного програмного гнізда безпосередньо з ім'ям використовується функція bind:

bind (sd, socknm, socknlen);

Здесь sd — дескриптор раніше створеного програмного гнізда, socknm — адресу структури, що містить ім'я (ідентифікатор) гнізда, відповідає вимогам домену даного гнізда і використовуваного протоколу (зокрема, для домену системи UNIX ім'я є ім'ям об'єкта в файловою системі, при створенні програмного гнізда справді створюється файл), параметр socknlen містить довжину в байтах структури socknm (цей параметр необхідний, оскільки довжина імені може дуже різнитися для різних комбінацій «домен-протокол »).

С допомогою функції connect процесс-клиент затребувана систему зв’язатися до існуючого програмним гніздом (у процесса-сервера):

connect (sd, socknm, socknlen);

Смысл параметрів той самий, як в функції bind, однак у ролі імені вказується ім'я програмного гнізда, які мають перебувати в інший боці комунікаційного каналу. Для нормального виконання функції необхідно, аби в гнізда з дескриптором sd і в гнізда безпосередньо з ім'ям socknm були однакові домен і протокол. Якщо тип гнізда з дескриптором sd є датаграммным, то функція connect використовують тільки для інформування системи про адресі призначення пакетів, які надалі будуть посилатися з допомогою функції send; ніякі дії з встановленню з'єднання перетворені на цьому випадку не виробляються.

Функция listen варта інформування системи у тому, що процесс-сервер планує встановлення віртуальних сполук через вказане гніздо:

listen (sd, qlength);

Здесь sd — це дескриптор існуючого програмного гнізда, а значенням параметра qlength є максимальна довжина черги запитів встановлення сполуки, які мають буферизоваться системою, наразі їх не вибере процесс-сервер.

Для вибірки процессом-сервером чергового запиту встановлення з'єднання з зазначеним програмним гніздом служить функція accept:

nsd = accept (sd, address, addrlen);

Параметр sd задає дескриптор існуючого програмного гнізда, котрій раніше вона була виконано функція listen; address свідчить про масив даних, куди мусить бути вміщена інформація, характеризує ім'я програмного гнізда клієнта, із боку якого надходить запит встановлення сполуки; addrlen — адресу, яким перебуває довжина масиву address. Якщо на момент виконання функції accept чергу запитів встановлення сполук порожня, то процесс-сервер відкладається до надходження запиту. Виконання функції призводить до встановленню віртуального сполуки, та її значенням є новий дескриптор програмного гнізда, який має використовуватися під час роботи через дане з'єднання. За адресою addrlen поміщається реальний розмір масиву даних, що записані за адресою address. Процесс-сервер може й далі «слухати «такі запити на з'єднання, користуючись встановленим з'єднанням.

Для передачі і прийому даних через програмні гнізда з установленою віртуальним з'єднанням використовуються функції send і recv:

count = send (sd, msg, length, flags);

count = recv (sd, buf, length, flags);

В функції send параметр sd задає дескриптор існуючого програмного гнізда з установленою з'єднанням; msg свідчить про буфер з цими, які слід послати; length задає довжину цього буфера. Найбільш корисним допустимим значенням параметра flags є значення із символічним ім'ям MSG_OOB, завдання якого означає потреба в позачергової посилці даних. «Позачергові «повідомлення посилаються крім нормального для даного сполуки потоку даних, обганяючи все непрочитані повідомлення. Потенційний одержувач даних може мати простий спеціальний сигнал й під час його обробки негайно прочитати позачергові дані. Яке значення функції дорівнює кількості реально посланих байтів й у нормальних ситуаціях збігається з значенням параметра length.

В функції recv параметр sd задає дескриптор існуючого програмного гнізда з установленою з'єднанням; buf свідчить про буфер, у який варто помістити прийняті дані; length задає максимальну довжину цього буфера. Найбільш корисним допустимим значенням параметра flags є значення під символічною ім'ям MSG_PEEK, завдання якого призводить до перепису сполучення користувальницький буфер без його видалення з системних буферів. Яке значення функції є числом байтів, реально поміщених у buf.

Заметим, у разі використання програмних гнізд з віртуальним з'єднанням замість функцій send і recv можна використовувати звичайні файлові системні виклики read і write. Для програмних гнізд вони виконуються абсолютно аналогічно функцій send і recv. Це дозволяє створювати програми, які залежать від цього, працюють вони зі звичайними файлами, програмними каналами чи програмними гніздами.

Для посилки і прийому повідомлень в датаграммном режимі використовуються функції sendto і recvfrom:

count = sendto (sd, msg, length, flags, socknm, socknlen);

count = recvfrom (sd, buf, length, flags, socknm, socknlen);

Смысл параметрів sd, msg, buf і lenght аналогічний змісту однойменних параметрів функцій send і recv. Параметри socknm і socknlen функції sendto задають ім'я програмного гнізда, у якому посилається повідомлення, і може бути опущені, якщо викликалася функція connect. Параметри socknm і socknlen функції recvfrom дозволяють серверу отримати ім'я пославшего повідомлення процесса-клиента.

Наконец, для негайної ліквідації встановленого сполуки використовується системний виклик shutdown:

shutdown (sd, mode);

Вызов цієї функції означає, що слід негайно зупинити комунікації або з боку що посилає процесу, або з боку приймаючої процесу, або по обидва боки (залежно від значення параметра mode). Дії функції shutdown від дій функції close тим, що, по-перше, виконання останньої «гальмується «до закінчення спроб системи доставити вже відправлені повідомлення. По-друге, функція shutdown розриває з'єднання, але з ліквідує дескриптори раніше з'єднаних гнізд. Для остаточної від їхньої ліквідації однаково потрібно виклик функції close.

Замечание: приведений у цьому пункті інформація може бути кілька відрізнятиметься від вимог реально використовуваної вами системи. У це належить до символічним іменам констант. Постійна біда користувачів ОС UNIX у тому, що з версії до версії змінюються символічні імена і імена системних структурних типів.

Потоки (streams)

Здесь нам майже нічого додати матеріалу, наведеному в п. 2.7.1. За підсумками використання механізму потокових мережевих драйверів в UNIX System V створена бібліотека TLI (Transport Layer Interface), забезпечує транспортний сервіс з урахуванням стека протоколів TCP/IP. Можливості цього пакета перевищують згадані вище можливості програмних гнізд й, звісно, дозволяють організовувати різноманітні види комунікації процесів. Проте розмаїття та складність набору функцій бібліотеки TLI неможливо в подробицях описати в цього курсу. З іншого боку, TLI належить, скоріш, немає темі ОС UNIX, а до цієї теми реалізацій семиуровневой моделі ISO/OSI. Тому на випадок необхідності ми рекомендуємо користуватися технічної документацією по використовуваному варіанту ОС UNIX чи читати спеціальну літературу, присвячену мережним можливостям сучасних версій UNIX.

Показати весь текст
Заповнити форму поточною роботою