Управління процесами
Другим по значущості поняттям в операційній системі (ОС) є поняття процесу. Процес - сутність, що визначається по-різному. Це може бути - "впорядкований набір команд і належать йому ресурсів". З точки зору ОС UNIX, процес - це об'єкт, зареєстрований в спеціальній таблиці процесів. Структура цієї таблиці така: вона позиційна (як практично і всі таблиці в UNIX), тобто номер запису в таблиці - є ідентифікатор процесу "PID". Формуються процеси з 0 до N-1, де N - граничне число процесів, що система може одночасно обробляти. Це параметр налаштування ОС.
Розглянемо інформативне навантаження таблиці. У рядку-запису таблиці знаходиться посилання на контекст процесу, там же знаходиться посилання на тіло процесу. Тілом процесу ми будемо називати набір команд і даних, якими оперує процес.
Контекст процесу - атрибут, який присутній практично у всіх ОС, в різних ОС він може називатися по-різному. Контексті всіх процесів розміщуються в адресному просторі ОС і містять оперативну інформацію про стан процесу та поточну інформацію, пов'язану з процесом і його запуском.
Контекст містить:
номера користувачів та групи;
покажчик на індексний дескриптор поточного каталогу;
специфічні умови роботи процесу:
- обробка сигналів;
Розглянемо це докладніше. В ОС UNIX кожен процес може послати іншому процесу деякий вплив, який називають "сигнал"; відповідно, якщо процес-відправник має право передати сигнал процесу-одержувачу, то при виконанні передачі в останньому виникає подія, пов'язана з сигналом.
Ця подія дуже схоже на переривання, що виникає в апаратурі обчислювальної системи. В ОС є набір сигналів, які можуть передавати один одному процеси; перелік сигналів описаний у файлі "signal.h". Відправник може подати певним чином команду ОС, що він передає сигнал з заданим номером процесу-одержувачу, процес-отримувач може прореагувати на сигнал трьома способами: 1) припинення виконання, причиною якого є прийшов сигнал; 2) ігнорування сигналу (тут слід зазначити, що ігнорувати можна далеко не всі сигнали); 3) викликається зумовлена процесом функція, яка може виконати якісь дії; повернення з цієї функції здійснюється в точку приходу сигналу.
- інформація про відкриті в процесі файлах;
- інформація про поточний стан процесу на випадок його припинення;
Зупиняючи виконання процесу, ОС "упрятивает" у відповідний контекст інформацію, потрібну для його продовження: режими програми в момент припинення, стан регістрів, адреса точки переривання.
Тіло процесу, - як уже було сказано, можна представити у вигляді об'єднання сегменту тексту (коди) і сегменту даних. Розвинені ОС дозволяють розміщувати сегменти тексту і даних в різних, що не залежать один від одного, місцях оперативної пам'яті. Це добре, тому що замість одного великого шматка пам'яті нам потрібно два маленьких. Але ще краще наступне - така організація дозволяє використовувати сегмент коду повторно. У системі допускається існування ще одного процесу з власним контекстом, сегментом даних, але у якого спільний з іншими процесами сегмент коду.
Якщо k користувачів викликають один текстовий редактор, то в системі знаходиться одна копія цього редактора і k копій сегмента даних і контекстів (копії, треба відмітити, не ідентичні). Це річ корисна, тому що звідси відразу ж можна збільшити "розум" планувальника відкачування (він може, наприклад, відкачувати сегмент даних, а не сегмент тексту).
Ми перерахували не всю інформацію контексту, і надалі ця інформація буде доповнюватися і уточнюватися.
Ми говорили, яким чином в UNIX-e можна створити копію поточного процесу, - це функція fork (), яка працює в такий спосіб:
fork ():> 0 PID синів процесу (ми знаходимося в процесі-батька)
= 0 (ми знаходимося в процесі-сина)
=- 1 сталася помилка - неможливо створити новий процес (залишаємося в процесі-батька), ця помилка може виникнути при нестачі місця в таблиці процесів, при нестачі місця в системних областях даних і т.п.
Система підтримує родинні взаємини між процесами, це означає, що існують деякі функції, характерні для роботи з процесами, які притаманні процесам, що є родичами. При породження синівської процесу з використанням fork () породжений процес успадковує:
Оточення - при формуванні процесу йому передається деякий набір параметрів-змінних, використовуючи які, процес може взаємодіяти з операційним оточенням (інтерпретатором команд і т.д.);
Файли, відкриті в процесі-батька, за винятком тих, яким було заборонено передаватися спеціальним параметром при відкритті;
Способи обробки сигналів;
Дозвіл переустановки чинного ідентифікатора користувача (це те, що пов'язано з s-bit'ом)
Усі приєднані колективні сегменти пам'яті - у нас є механізм управління розділяються ресурсами, і в якості одного з поділюваних ресурсів може виступати оперативна пам'ять, в ній може бути виділений сегмент, до якого одночасно мають доступ декілька процесів. При формуванні синівської процесу ця частина пам'яті також буде успадкована;
Поточний робочий каталог і кореневий каталог;
Не успадковується при створенні нового процесу ідентифікатор процесу (чому - очевидно).
Повертаючись до функції fork (), слід зауважити, що вона сама по собі безглузда, бо застосування такого створення точної копії процесу знайти дуже складно. Тому функція fork () використовується спільно з групою функцій exec (...). Ця група поєднує в собі функції, які частиною свого імені мають слово "exec" і виконують приблизно однакові дії, (набором або інтерпретацією параметрів).
Суть функцій exec () - в наступному: при зверненні до неї відбувається заміна тіла поточного процесу, воно замінюється згідно з ім'ям файлу, що виконується, зазначеного одним з параметрів функції. Функція повертає "-1", якщо дія не виконано, і код, відмінний від "-1", якщо операція пройшла успішно. Тут слід відзначити такий факт - в UNIX-і при роботі із системними викликами іноді виникають діагностичні повідомлення у вигляді коду відповіді, які неможливо розділити на конкретні причини, що викликали повернення цього коду. Прикладом цього є коди "-1" для fork () і exec (...). Для того щоб обійти цю незручність, слід включити до програми файл "errno.h", і після цього при виникненні відмов у виконанні системних викликів у змінній "errno" буде код конкретної причини відмови виконання замовлення. Всілякі коди відмови описані в самому "errno.h".
Давайте наведемо невеликий приклад. Ми напишемо програму, яка буде запускати файли, імена яких перераховані при виклику.
main (argc, argv)
int argc;
char * argv;
(int i, pid;
for (i = 1; i0, тобто ми перебуваємо в процесі-батька, то продовжуємо створювати синівських процеси, поки є аргументи.
Як ілюстрацію роботи fork () можна привести наступну картинку:
Тут процес із PID = 105 створюється процесом з PID = 101.
Також слід зазначити, що якщо побивається процес-батько, то новим батьком стає 1-ий процес ОС.
Зв'язка fork/exec за своєю потужністю сильніше, ніж, якби була єдина функція, яка відразу б створювала новий процес і заміщав б його вміст. Fork/exec дозволяють вставити між ними ще деяку програму, яка буде містити якісь корисні дії.
Ми почали розглядати організацію процесів. Ми на пальцях показали, як розміщується інформація в ОС. У принципі, вся інформація, яка відображає оперативне стан ОС, а також програми ОС, які керують цією інформацією і найбільш важливими пристроями, складають ядро ОС.
Ядро ОС - програма, функцією якої є управління базовими об'єктами системи (для UNIX-а це два об'єкти - файл і процес). Ядро у своєму тілі розміщує необхідні таблиці даних. Ядро вважається деякою неподільні частиною ОС. Воно зазвичай працює в режимі супервізора, всі інші функції ОС можуть працювати і в інших режимах.
Минулого лекції ми почали говорити про процеси в операційній системі UNIX. Можна однозначно сказати про те, що процеси та механізми управління процесами в операційній системі - це одна з принципових особливостей операційної системи UNIX, тобто тих особливостей, які відрізняли систему при створенні і відрізняють її до цих пір. Більше того, незважаючи на старання пана Гейтса, ситуація така, що він повторює ті програмні інтерфейси, які використовуються для взаємодії управління процесами, а не фірми розробники UNIX-ів повторюють ті інтерфейси, які з'явилися в Windows. Першість операційної системи UNIX очевидно.
Ми говорили про те, що процес в UNIX-ті - це є щось, що зареєстровано в таблиці процесів. Відповідно кожен запис у таблиці процесів має номер. Номери йдуть від нуля до деякого граничного значення, яка зумовлена при встановленні системи. Номер в таблиці процесів - це є, так званий, ідентифікатор процесу, що в системі позначається PID. Відповідно, переважна більшість дій, які можна виконати за процесом, виконуються за допомогою вказівки ідентифікатора процесу. Кожен процес характеризується контекстом процесу. Це блок даних, що характеризує стан процесу, в тому числі у цьому блоці даних вказується інформація про відкриті файли, про правила обробки подій, що виникають у процесі. У цьому наборі даних зберігається інформація, яка утворюється при повному "упрятиваніі" процесу при перемиканні системи з процесу на процес. Тобто коли відбувається з тієї чи іншої причини перемикання виконання з одного процесу на інший, для того щоб можна було відновити роботу процесу, якийсь набір даних розміщується в контексті процесу. Цей набір даних містить в собі вміст регістровий пам'яті, деякі режими, які встановила програма і в які втрутився процесор (наприклад, вміст регістра результату), точку повернення з переривання. Плюс - контекст містить багато корисної інформації, про яку ми будемо говорити пізніше.
Ми говорили про те, що в деякому розумінні визначено поняття тіла процесу. Тіло процесу складається з двох сегментів: сегменту тексту і сегменту даних. Сегмент тексту - це частина даних процесу, які включають в себе код виконуваної програми. Сегмент даних - це ті простору оперативної пам'яті, які можуть статично містити дані. Ми говорили, що в системі є можливість мати розділені сегменти тексту і сегменти даних. У свою чергу, система дозволяє з одним сегментом тексту пов'язувати довільну групу сегментів даних. Це, зокрема, буває корисно, коли в системі одночасно працюють кілька однакових процесів.
Принципово важлива річ, пов'язана з організацією управлінням процесами, - механізм fork/exec. При зверненні до функції fork відбувається створення нового процесу, який є копією поточного процесу. Незначні відмінності цих процесів є в ідентифікаторі процесів. Можливі деякі відмінності у вмісті контексту процесу.
Функція exec дозволяє замінювати тіло процесу, тобто при зверненні до цієї функції, в разі успішного її виконання, тіло процесу змінюється на нове вміст, який вказано як аргументи функції exec у вигляді імені деякого файлу. Ми говорили про те, що сама по собі функція fork майже безглузда. Спробуємо вловити зміст функції exec - можна виконувати в рамках одного процесу кілька завдань. Виникає питання: чому формування цього процесу роздроблений на дві функції fork і exec? Чим це обгрунтовано? У багатьох системах є одна функція, яка формує процес з заданим вмістом. Справа в тому, що при зверненні до функції fork, як уже неодноразово було сказано, створюється копія процесу, у тому числі процес-син успадковує всі ті файли, які були відкриті в процесі батька і багато інших права і привілеї. Буває ситуація, коли не хотілося б, щоб спадкоємець набував всі особливості батька. І є можливість між виконанням функцій fork і exec виконати якісь дії щодо закриття файлів, відкриття нових файлів, за перевизначення чого-небудь і т.д. Зокрема, ви при практичних заняттях повинні освоїти відладчик системи deb. Яка суть його роботи?
Хай є процес-відладчик deb; запускається процес, який регламентуватиме, і, передаючи деяку інформацію від відладчика до налагоджувати процесу, можна робити налагодження. Але налагоджувати можна тільки той процес, який дозволив себе налагоджувати. Саме тут використовується роздвоєння fork/exec. Спочатку я роблю копію свого процесу deb '
, Після цього я дозволяю проводити трасування поточного процесу, а після цього я запускаю функцію exec з налагоджують програму. Виходить ситуація, що в процесі утворюється саме та програма, яку треба налагодити, і вона, не підозрюючи про це, вже працює в режимі налагодження.
Завантаження операційної системи та освіта початкових процесів b>. Сьогодні ми з вами поговоримо про завантаження операційної системи і освіту початкових процесів. При включенні обчислювальної системи з ПЗП (постійно запам'ятовуючого пристрою) запускається апаратний завантажувач. Здійснюється читання нульового блоку системного пристрою. З цього нульового блоку запускається програмний завантажувач ОС UNIX. цей програмний завантажувач здійснює пошук та запуск файлу з ім'ям unix, який є ядром операційної системи. У початковий момент відбуваються дії ядра з ініціалізації системи. Це означає, що відповідно до параметрів налаштування системи, формуються необхідні таблиці, не започатковано деякі апаратні інтерфейси (ініціалізація диспетчера пам'яті, годин, і т.д.). Після цього ядро системи створює процес № 0. При цьому нульовий процес є виродженим процесом з точки зору організації інших процесів. Нульовий процес не має коду, він містить тільки контекст процесу. Вважається, що нульовий процес активний, коли працює ядро, і пасивний у всіх інших випадках.
До моменту утворення нульового процесу в системі вже утворені таблиці, проведена необхідна ініціалізація, і система близька до готовності працювати. Потім ядро копіює нульовий процес у перший процес. При цьому під першим процес уже резервуються ті ресурси, які необхідні для повноцінного процесу, тобто для нього резервуються сегмент контексту процесу, і для нього резервується пам'ять для розміщення тіла процесу. Після цього в першу процес завантажується програма init. При цьому запускається диспетчер процесів. І ситуація така: існує єдиний процес, реально готовий до виконання. Процес init реально завершує запуск системи.
Запуск системи може відбуватися в двох режимах. Перший режим - це однокористувацький режим. У цьому випадку процес init завантажує інтерпретатор команд shell і асоціює його з консольним терміналом, а також запускає стартовий командний файл/etc/rc. Цей файл містить довільні команди, які може встановлювати адміністратор системи, якщо він вважає за необхідне виконати їх при старті системи. Це можуть бути команди, припустимо, запуску програми тестування цілісності файлової системи або перевірки розкладу і, в залежності від розкладу, запуску процесу, який буде архівувати файлову систему і т.д. Тобто в цьому командному файлі в загальному випадку можуть перебувати довільні команди, встановлені адміністратором системи. При цьому якщо система запускається в одного користувача режимі, на консольний термінал подається інтерпретатор команд shell і вважається, що консольний термінал знаходиться в режимі супервізора (супер) з усіма правами, які можна надати адміністратору системи.
Другий режим - розрахований на багато користувачів. Якщо однокористувацький режим звичайно використовується в ситуаціях, коли в системі сталася аварійна ситуація і необхідні дії адміністратора системи або системного програміста, то багато-режим - це штатний режим, який працює в нормальній ситуації. При многопользовательском режиме процес init запускає для кожного активного терміналу процес getty. Список терміналів береться з деякого текстового файлу, а їх активність чи пасивність - це прерогатива апаратних властивостей конкретного терміналу і драйвера, який обслуговує цей термінал (коли ви вмикаєте термінал, йде сигнал за відповідним інтерфейсу про включення нового пристрою; система здійснює ідентифікацію цього пристрою відповідно з портом, до якого підключений цей термінал).
Процес getty при запуску відразу ж запитує login. Копія процесу getty працює на один сеанс роботи з користувачем, тобто користувач підтвердив своє ім'я і пароль, виконує якісь дії, і, коли він виконує команду завершення роботи, то копія процесу getty завершує свою роботу. Після завершення роботи процесу getty, пов'язаного з конкретним терміналом, запускається нова копія процесу getty.
Ось така схема. Це нетрадиційні прийоми формування процесів в UNIX-е. Нетрадиційно формується нульовий процес (і він сам по собі нетрадиційний), нетрадиційно формується перший процес (який також нетрадиційний). Всі інші процеси працюють за схемою fork/exec.
Ефективні та реальні ідентифікатори процесу b>. З кожним процесом пов'язано три ідентифікатора процесу. Перший - ідентифікатор самого процесу, який був отриманий при формуванні. Другий - це т.зв. ефективний ідентифікатор (ЕІ). ЕІ - це ідентифікатор, пов'язаний з користувачем, що запустив цей процес. Реальний ідентифікатор (РІ) - це ідентифікатор, пов'язаний із занедбаним у вигляді процесу файлом (якщо я запускаю свій файл, то ЕІ і РІ будуть однакові, якщо я запускаю чужий файл, і у цього файлу є s-біт, то в цьому випадку РІ буде ідентифікатором власника файлу і це означає, що запущеному процесу будуть делеговані права цього власника).
Планування процесів в ОС UNIX. b> Планування грунтується на понятті пріоритету. Чим вище числове значення пріоритету, тим менше пріоритет. Пріоритет процесу - це параметр, який розміщений в контексті процесу, і за значенням цього параметра здійснюється вибір чергового процесу для продовження роботи або вибір процесу для його припинення. В обчисленні пріоритету використовуються дві складові - P_NICE і P_CPU. P_NICE - це призначена для користувача складова пріоритету. Вона успадковується від батька та може змінюватися з волі процесу. Змінюватися вона може тільки в бік збільшення значення (до деякого граничного значення). Тобто користувач може знижувати пріоритет своїх процесів. P_CPU - це системна складова. Вона формується системою таким чином: по таймеру через визначені періоди часу P_CPU збільшується на одиницю для процесу, що працює з процесором (коли процес відкачується на ВЗП, то P_CPU обнуляється).
Процесор виділяється тому процесу, у якого пріоритет є найменшим. Спрощена формула обчислення пріоритету така:
ПРІОРИТЕТ = P_USER + P_NICE + P_CPU
Константа P_USER розрізняється для процесів операційної системи та інших призначених для користувача процесів. Для процесів операційної системи вона дорівнює нулю, для процесів користувачів вона дорівнює деякому значенню (тобто "навішуються гирьки на ноги" процесам користувача, що б вони не "задавлівалі" процеси системи). Це дозволяє апріорі підвищити пріоритет системних процесів.
Схема планування свопінгу. b> Ми говорили про те, що в системі певним чином виділяється простір для області свопінгу. Є проблема. Є простір оперативної пам'яті, в якому знаходяться процеси, що обробляються системою в режимі мультипрограмування. Є область на ВЗП, призначена для відкачування цих процесів. В ОС UNIX (в модельному варіанті) свопирування здійснюється всім процесом, тобто відкачується не частина процесу, а весь. Це правило діє в переважній числі UNIX-ів, тобто свопінг в UNIX-е в загальному не ефективний. Спрощена схема планування підкачки грунтується на використанні певного пріоритету, який називається P_TIME і також знаходиться в контексті процесу. У цьому параметрі акумулюється час перебування процесу в змозі мультипрограмній обробки, або в області свопінгу.
При переміщенні процесу з оперативної пам'яті в область свопінгу або назад система обнуляє значення параметра P_TIME. Для завантаження процесу в пам'ять з області свопінгу вибирається процес з максимальним значенням P_TIME. Якщо для завантаження цього процесу немає вільного простору оперативної пам'яті, то система шукає серед процесів в оперативній пам'яті процес, що очікує введення/виводу і має максимальне значення P_TIME (тобто той, який знаходився в оперативній пам'яті довше за всіх). Якщо такого процесу немає, то вибирається просто процес з максимальним значенням P_TIME.
Ця схема не зовсім ефективна. Перша неефективність - це те, що обміни з оперативної пам'яті в область свопінгу відбуваються усім процесом. Друга неефективність (пов'язана з першою) полягає в тому, що якщо процес закрито через замовлення на обмін, то цей обмін реально відбувається не з свопірованним процесом, тобто для того щоб обмін нормально завершився, весь процес повинен бути повернутий в оперативну пам'ять. Це теж погано тому, що, якби свопінг відбувався блоками пам'яті, то можна було б відкачати процес без тієї сторінки, з якою треба змінюватися, а після обміну докачати з області свопінгу весь процес назад в оперативну пам'ять. Сучасні UNIX-и мають можливість Свопування не всього процесу, а якоїсь його частини.
Минулого лекції ми з вами подивилися, яким чином може здійснюватися планування в операційній системі UNIX. Ми з вами визначили, що в принципі планування в системі піддаються два типи процесів. Перший тип - це ті процеси, які перебувають в оперативній пам'яті і між якими відбувається поділ часу ЦП. Ми з'ясували, що цей механізм досить простий і будується на обчисленні деякого значення пріоритету. А що буде, якщо системна складова досягне максимального значення? У цьому випадку у процесу просто буде нижчий пріоритет. Другий тип процесів - процеси, які знаходяться на диску, - піддається планування свопінгу. Будь-який процес в системі може знаходитися в двох станах - або він весь відкачано на ВЗП, або він весь знаходиться в оперативній пам'яті. І в тому і в іншому випадку з процесом асоційоване деяке значення P_TIME, що росте по мірі знаходження процесу в цьому конкретному стані. Це значення обнуляється, коли процес змінює свій стан (тобто перекачується в оперативну пам'ять або назад). У свою чергу система використовує P_TIME як значення деякого пріоритету (чим більше це значення, тим більш імовірно, що процес змінить свій статус).
Виникало запитання, що є причиною для ініціації дії по докачка процесу з області свопінгу в оперативну пам'ять. Це питання не має однозначної відповіді, тому що в кожному UNIX-е це зроблено по-своєму. Є два рішення. Перше рішення полягає в тому, що при досягненні P_TIME деякого граничного значення операційна система починає намагатися його перекачати в оперативну пам'ять для подальшої обробки. Друге можливе рішення може полягати в тому, що є деяка умова на системну складову нульового процесу (нульовий процес - це ядро). Як тільки в системі виникає ситуація, що ядро починає працювати дуже багато, - це стає ознакою того, що система недовантажено, тобто у системи може бути багато процесів в оперативній пам'яті, але вони всі займаються обміном, і ЦП простоює. Система може в цій ситуації якісь процеси відкачати, а якісь ввести в мультипрограмному обробку.
Ми з вами говорили про те, що різні UNIX-и можуть по-різному представляти процес в ході його обробки. UNIX-и представляють тіло процесу як єдине ціле (і код, і дані), і всі переміщення здійснюються згідно з тим, що це єдине ціле. Деякі (сучасні) UNIX-и розглядають процес як об'єднання двох сегментів - сегменту коду і сегменту даних. З цим пов'язані проблеми запуску процесів, планування часу процесора і планування свопінгу.
При запуску якогось процесу система повинна зрозуміти, чи немає цього процесу в числі вже запущених, щоб не запускати зайвий сегмент коду, а прив'язати нові дані до вже функціонуючих сегменту коду. Це визначається досить просто - в контексті процесу є параметр, який містить значення ІД (індексного дескриптора) файлу, з якого був запущений даний процес. І коли система намагається завантажити новий процес (з файлу), то перед цим здійснюється перегляд контекстів існуючих процесів, і система дивиться, чи немає вже в оперативній пам'яті процесу з заданим ВД, тобто процесу, запущеного з того ж файлу. Аналогічно відбувається облік при Свопування, тобто спочатку Свопування віддаються сегменти даних, а потім можуть розглядатися кодові сегменти. Звертаю увагу, що при виконанні функції exec в контексті процесу зміниться відповідна інформація про ІД.
Нагадую, що мета нашого курсу не є вивчення того, як реалізована та чи інша функція в тій чи іншій версії системи UNIX. Ми хочемо подивитися, як це можна зробити, щоб у вас не виникало відчуття дива, коли ви бачите працюючу операційну систему, і вас не проймала тремтіння, що це щось надприродне. Все гранично просто. Є правило: чим більш системної є програма, тим більш прозорими мають бути алгоритми та використані ідеї. Мудровані програми живуть з працею, і це підтверджено практикою. Прозорі програми живуть довго. Приклад - UNIX - прозора програма, і приклад Windows - програма, побудована на дуже високому рівні, але там немає прозорості на всіх рівнях, і, на жаль, система має достатню кількість особливостей, які призводять до непередбачуваних результатів її роботи. Так скрізь. Якщо ми подивимося мови програмування - був зовсім фантастичний проект мови АДА, коли на конкурсній основі були утворені кілька професійних команд, які розробляли мова кінця XX століття. Він повинен був уміти робити все. Вийшла дуже гарна річ. З професійної точки зору, ця мова у всьому гарний, але він не знайшов практичного застосування, тому що складний. Абсолютно "бездарний" мова Сі існує і ще довго буде існувати. Те ж саме можна сказати про мови Вірта (це дядько, який придумав Паскаль, Модулі та Оберон) - вони теж не прижилися.
Процеси та взаємодія процесів
З цього моменту часу ми починаємо довго і наполегливо розглядати різні способи взаємодії процесів в операційній системі UNIX. Маленьке технічне додавання. Я зараз вам продекларірую два системні функції, якими ми будемо користуватися згодом. Це функції дублювання файлових дескрипторів (ФД).
int dup (fd); int dup2 (fd, to_fd);
int fd; int fd, to_fd;
Аргументом функції dup є файловий дескриптор відкритого в даному процесі файлу. Ця функція повертає -1 в тому випадку, якщо звернення не пропрацювала, і значення, більше або рівне нулю, якщо робота функції успішно завершилася. Робота функції полягає в тому, що здійснюється дублювання ФД в деякий вільний ФД, тобто можна як б продублювати відкритий файл.
Функція dup2 дублює дескриптор fd в деякий файловий дескриптор з номером to_fd. При цьому, якщо при зверненні до цієї функції ФД, в який ми хочемо дублювати, був зайнятий, то відбувається закриття файлу, що працює з цим ФД, і перевизначення ФД.
Приклад:
int fd;
char s [80];
fd = open ( "a.txt", O_RDONLY);
dup2 (fd, 0);
close (fd);
gets (s, 80);
Програма відкриває файл з ім'ям a.txt тільки на читання. ФД, який буде пов'язаний з цим файлом, що знаходиться в fd. Далі програма звертається до функції dup2, в результаті чого буде замінений стандартний ввід процесу на роботу з файлом a.txt. Далі можна закрити дескриптор fd. Функція gets прочитає чергову рядок з файлу a.txt. Ви бачите, що перевизначення здійснюється дуже просто.
Програмні канали. b> Спочатку кілька слів про концепцію. Є два процеси, і ми хочемо організувати взаємодію між цими процесами шляхом передачі даних від одного процесу до іншого. У системі UNIX для цієї мети використовуються т.зв. канали. З точки зору програми, канал є якась суть, що володіє двома файловими дескриптора. Через один ФД процес може писати інформацію на канал, через інший ФД процес може читати інформацію з каналу. Так як канал це щось, пов'язане з файловими дескриптора, то канал може передаватися в спадщину синівським процесам. Це означає, що два споріднених процесу можуть володіти одним і тим же каналом. Це означає, що якщо один процес запише якусь інформацію на канал, то інший процес може прочитати цю інформацію з цього ж каналу.
Особливості роботи з каналом. Під зберігання інформації, що передається через канал, виділяється деякий фіксований обсяг оперативної пам'яті. У деяких системах цей буфер може бути продовжений на зовнішню пам'ять. Що відбувається, якщо процес хоче записати інформацію на канал, а буфер переповнений, або прочитати інформацію з каналу, а в буфері немає ще даних? В обох випадках процес припиняє своє виконання і чекає, поки не звільниться місце або, відповідно, поки в каналі не з'явиться інформація. Треба відмітити, що в цих випадках робота процесу може змінюватися в залежності від встановлених параметрів, які можна змінювати програмно (і реакцією на ці ситуації може бути не очікування, а повернення деякого коду відповіді).
Давайте подивимося, як ці концепції реалізуються в системі. Є функція pipe. Аргументом цієї функції повинен бути вказівник на масив двох цілих змінних.
int pipe (pipes);
int pipes [2];
Нульовий елемент масиву після звернення до функції pipe отримує ФД для читання, перший елемент цього масиву отримує ФД для запису. Якщо немає вільних ФД, то ця функція повертає -1. Ознака кінця файлу для зчитує дескриптора не буде отримано до тих пір, поки не закриті всі дескриптори, пов'язані із записом у цей канал. b> Розглянемо невеликий приклад:
char * s = "Це приклад";
char b [80];
int pipes [2];
pipe (pipes);
write (pipes [1], s, strlen (s) +1);
read (pipes [0], s, strlen (s) +1);
Це приклад копіювання рядка (зрозуміло, що так копіювати рядки не треба, і взагалі ніхто функцією pipe в межах одного процесу не користується). У цьому прикладі і в наступних не обробляються випадки відмови. Тепер давайте розглянемо більш змістовний приклад. Напишемо приклад програми, яка запустить і пов'яже каналом два процеси:
main ()
(
int fd [2];
pipe (fd);/* в батьківському процесі утворюючи два дескриптора каналу * /
if (fork ())/* утворюючи процес-син, у якого будуть ті ж дескриптори * /
(/ * ця частина програми відбувається в процесі батька-* /
dup2 (fd [1], 1);/* замінюємо стандартний висновок висновком в канал * /
close (fd [1 ]);/* закриваємо дескриптори каналу * /
close (fd [0 ]);/* тепер весь висновок тепер буде відбуватися в канал * /
execl ( "/ bin/ls", "ls", (char *) 0);/* замінюємо тіло батька на ls * /
)/* звідси починає працювати процес-син * /
dup2 (fd [0], 0);/* в процесі сина все робимо аналогічно * /
close (fd [0]);
close (fd [1]);
b>
execl ( "/ bin/wc", "wc", (char *) 0);
)
Цей приклад пов'язує конвеєром дві команди - ls і wc. Команда ls виводить вміст каталогу, а команда wc підраховує кількість рядків. Результатом виконання нашої програми буде підрахунок рядків, виведених командою ls.
У батьківському процесі запущено процес ls. Всю вихідну інформацію ls завантажує на канал, тому що ми асоціювали стандартний пристрій виводу з каналом. Далі ми у сина запустили процес wc, у якого стандартний пристрій введення (тобто те, звідки wc читає інформацію) пов'язане з дескриптором читання з каналу. Це означає, що все те, що буде писати ls свого стандартний пристрій виводу, надходитиме на стандартний пристрій введення команди wc.
Ми говорили про те, що для того, щоб канал працював належним чином, і читає дескриптор отримав ознака кінця файла, повинні бути закриті всі друкарські дескриптори. Якби в нашій програмі не була б вказана виділена рядок, то процес, пов'язаний з wc завис б, тому що в цьому випадку функція, що читає з каналу, не дочекається ознаки кінця файлу. Вона буде чекати його нескінченно довго. У процесі батька підкреслену рядок можна було б не вказувати, тому що дескриптор закрився б при завершенні процесу, а в процесі сина такий рядок потрібна. Тобто висновок такий: перед завершенням роботи повинні закриватися всі дескриптори каналів, пов'язані із записом.
Каналом можна пов'язувати тільки споріднені процеси. Технічно м?? жно зв'язувати кілька процесів одним каналом, але можуть виникнути проблеми.
Лекція № 14
Сигнали в системі UNIX
Розглянемо взаємодію між процесами за допомогою прийому-передачі сигналів. Ми вже говорили про те, що в системі UNIX можна побудувати аналогію механізму переривань з деяких подій, які можуть виникати при роботі процесів.
Ці події так само, як і переривання, однозначно визначені для конкретної версії ОС, тобто набір сигналів визначений. Виникнення сигналів майже так само, як і виникнення переривань, може відбуватися з наступних причин:
деякий подія усередині програми, наприклад, ділення на нуль або переповнення;
подія, пов'язана з приходом деякою інформацією від пристрою, наприклад, подія, пов'язана з передачею від клавіатури комбінації "Ctrl + C";
подія, пов'язана з впливом одного процесу на інший, наприклад, "SIG_KILL".
Система має фіксований набір подій, які можуть виникати. Кожна подія має своє унікальне ім'я; ці імена зазвичай єдині для всіх версій UNIX. Такі імена називаються сигналами.
Перелік сигналів знаходиться в include-