
- •Поняття операційної системи
- •Операційна система як розширена машина
- •Операційна система як менеджер ресурсів
- •Історія розвитку операційних систем
- •Перше покоління (1945-1955): електронні лампи і комутаційні панелі
- •Друге покоління (1955-1965): транзистори і системи пакетної обробки
- •Третє покоління (1965-1980): інтегральні схеми і багатозадачність
- •Четверте покоління (з 1980 року по наші дні): персональні комп'ютери
- •Історія minix 3
- •Основні концепції
- •Процеси
- •Оболонка
- •Системні виклики
- •Системні виклики для управління процесами
- •Системні виклики для управління сигналами
- •Системні виклики для управління файлами
- •Системні виклики для управління каталогами
- •Системні виклики для захисту
- •Системні виклики для управління часом
- •Структура операційної системи
- •1.5.2. Багаторівневі системи
- •1.5.3. Віртуальні машини
- •1.5.4. Екзоядра
- •1.5.5. Модель клієнт-сервер
- •2.1.1. Модель процесів
- •2.1.2. Створення процесів
- •2.1.3. Завершення процесів
- •2.1.4. Ієрархії процесів
- •2.1.5. Стани процесів
- •2.1.6. Реалізація процесів
- •2.1.7. Програмні потоки
- •2.2. Взаємодія між процесами
- •5.1. Файли
- •5.1.1. Іменування файлів
- •5.1.2. Структура файлу
- •5.1.3. Типи файлів
- •5.1.4. Доступ до файлів
- •5.1.5. Атрибути файлів
- •5.1.6. Операції з файлами
- •5.2. Каталоги
- •5.2.1. Прості каталоги
- •5.2.2. Ієрархічні системи каталогів
- •5.2.3. Шляхи
- •5.2.4. Операції з каталогами
- •5.3. Реалізація файлової системи
- •5.3.1. Структура файлової системи
- •5.3.2. Реалізація файлів
- •5.3.4. Організація дискового простору
2.1.7. Програмні потоки
У традиційних операційних системах у кожного процесу є адресний простір і один потік управління. По суті, це майже повністю визначає процес. Проте нерідко виникають ситуації, в яких бажано мати декілька потоків управління, квазіпараллельно
виконуються в одному адресному просторі так, як ніби вони є окремі процеси (за винятком спільності адресного простору). Такі потоки управління зазвичай називаються програмними потоками, або легковагими процесами.
З одного боку, процес можна розглядати як згруповано, пов'язаних
пов'язані між собою ресурси. Процес має адресний простір, що містить текст програми і дані, а також інші ресурси, наприклад відкриті файли, дочірні процеси, що діють аварійні сигнали, обробники сигналів, облікову інформацію та інше . Угруповання ресурсів у вигляді процесу спрощує управління ними. З іншого боку, з процесом пов'язане поняття програмного потоку. Програмний потік має лічильник команд, який вказує на наступну виконувану команду, регістри, що містять робочі змінні, а також стек, в якому зберігається послідовність виконання. Хоча програмний потік і повинен виконуватися в рамках якого-небудь процесу, програмний потік і його процес - не одне і те ж, і їх слід розглядати окремо. Процеси – це механізм угруповання ресурсів, а програмні потоки - елементи, виконувані центральним процесором за певним планом.
Програмні потоки доповнюють модель процесу можливістю паралельного
виконання в рамках одного процесного оточення з високим ступенем
незалежності.
Як приклад програми, розрахованого на багатопоточність,
розглянемо веб-браузер. Веб-сторінки часто містять безліч картинок невеликих
невеликого розміру. Для відтворення кожної картинки браузер повинен
встановити окреме з'єднання з сайтом, якому належить сторінка, і послати
запит на її отримання. Установка і розрив з'єднань забирають багато
часу. За підтримки браузером багатопоточності можна завантажувати кілька
картинок одночасно, що значно прискорює завантаження сторінки,
оскільки при невеликому розмірі більшості зображень установка з'єднань
віднімає більше часу, ніж передача даних. Коли в одному адресному просторі є кілька програмних потоків, то деякі з полів, показаних в табл. 2.1, відносяться вже не до процесів, а до окремих програмним потокам. Тому необхідна додаткова таблиця, кожен запис в якій буде описувати окремий потік. Серед інших в цій таблиці повинні бути поля для лічильника команд, регістрів і стану потоку. Лічильник команд потрібен тому, що програмні потоки, як і процеси, можуть припинятися і відновлювати свою роботу. Поля регістрів необхідні з тієї причини, що значення регістрів припиненого
програмного потоку необхідно зберігати. Нарешті, програмні потоки, як
і процеси, можуть перебувати в стані виконання, готовності і блокування.
У табл. 2.2 перераховані деякі поля, пов'язані з процесам і програмним потокам.
Таблиця 2.2. У першій колонці перераховані поля, спільно використовуються усіма
програмними потоками процесу, а в другій колонці - поля, пов'язані
до окремих програмним потокам
Процес Програмний потік
Адресний простір Лічильник команд
Глобальні змінні Регістри
Відкриті файли Стек
Дочірні процеси Стан
Діючі аварійні сигнали
Сигнали та їх обробники
Облікова інформація
Іноді операційна система не піклується про програмні потоках. Іншими
словами, управління програмними потоками відбувається цілком у
режимі користувача. Наприклад, коли програмний потік блокується, то, перед
тим як зупинитися, він вирішує, який програмний потік повинен
виконуватися наступним, і запускає його. Існують і широко використовуються кілька
бібліотек для підтримки користувальницьких програмних потоків, в тому числі
пакети P-Threads від POSIX і C-Threads від Mach.
В інших випадках ОС враховує існування безлічі потоків, і коли
один програмний потік переходить в стан блокування, система вибирає
для запуску наступний потік в тому ж самому процесі або в іншому. Щоб
підтримувати таку функціональність, ядро системи повинно зберігати таблицю
всіх програмних потоків в системі, на зразок таблиці процесів.
Хоча, на перший погляд, обидва варіанти можуть здатися рівносильними, між
ними є помітна різниця в продуктивності. Перемикання потоків
відбувається набагато швидше, якщо воно відбувається без участі ядра. Цей факт -
серйозний аргумент за користувальницькі програмні потоки. У той же час,
коли один з користувацьких програмних потоків блокується системою
(Наприклад, він чекає завершення операції введення-виведення або повинен
обробити помилку відсутності сторінки), то блокується весь процес, оскільки ядро
не підозрює про існування декількох послідовностей команд. Це -
сильний аргумент за потоки, керовані ядром [10]. Як наслідок,
використовуються обидва механізму, існує також безліч гібридних схем [1].
Хоча програмні потоки часто бувають корисними, вони суттєво
ускладнюють програмну модель. Уявіть собі системний виклик fork. Якщо
батьківський процес складався з безлічі програмних потоків, чи це
властивість поширюватися на дочірній процес? Якщо ні, то можливо
неправильне функціонування процесу, оскільки всі програмні потоки можуть
виявитися необхідними.
Але що станеться, якщо потік батьківського процесу буде блокований виклику
викликом read з клавіатури, а у дочірнього процесу стільки ж програмних потоків,
скільки у батьківського? Чи будуть в цьому випадку блоковані два програмні
потоку - один з батьківського процесу, інший з дочірнього? І якщо з
клавіатури надійде рядок, чи отримають обидва програмних потоку її копію? Або
тільки один - тоді який? Та ж проблема виникає при роботі з відкритими
мережевими з'єднаннями.
Інший клас проблем пов'язаний з тим, що програмні потоки спільно
використовують велику кількість структур даних. Що станеться, якщо один
програмний потік закриє файл в той час, коли інший зчитує з нього дані?
Уявіть собі, що одному програмному потоку перестало вистачати пам'яті, і він
просить виділити додаткову. На півдорозі відбувається перемикання потоків,
і вже новий програмний потік виявляє, що йому не вистачає пам'яті, і
запитує її для себе. У цій ситуації пам'ять може бути виділена двічі.
Ще одна проблема пов'язана з повідомленнями про помилки. В UNIX після
системного виклику система поміщає інформацію про стан операції в глобальну
змінну errno. А що станеться, якщо відразу після того, як перший
програмний потік зробить системний виклик, управління буде передано іншому
потоку, який також зробить системний виклик і затре значення глобальної
змінної?
Тепер розглянемо сигнали. Одні з них замикаються на програмні потоки,
тоді як інші - ні. Наприклад, якщо програмний потік виконує запит
alarm, результуючий сигнал за логікою має повернутися до цього програмного потоку. Однак коли програмні потоки реалізовані в просторі
користувача, ядро нічого не знає про їхнє існування і навряд чи направить
сигнал за призначенням. Ситуація ще більше ускладнюється, якщо у процесу може
бути тільки один необроблений аварійний сигнал, а кілька програмних
потоків виконують запит alarm незалежно один від одного.
Інші сигнали, такі як переривання з клавіатури, не пов'язані з
програмними потоками. Хто повинен їх перехоплювати? Один спеціально виділений
для цього програмний потік? Всі програмні потоки? Просто черговий
програмний потік? Що станеться, якщо один програмний потік змінить ОБГ
розробник сигналу, не попередивши про це інші програмні потоки?
Остання проблема, породжувана програмними потоками, - управління
стеками. У багатьох системах при переповненні стека процесу ядро автоматично
збільшує його. Якщо у процесу кілька програмних потоків, стеків теж
повинно бути декілька. Якщо ядро не знає про існування цих стеків, воно не
може їх автоматично нарощувати при переповненні. Ядро може навіть не
пов'язати помилки пам'яті з переповненням стеків.
Зрозуміло, ці проблеми переборні, але на їх прикладі добре видно, що
введення програмних потоків в існуючу систему без ретельної і
продуманої реконструкції всієї системи не має сенсу. Принаймні
доведеться змінити семантику системних запитів і переписати бібліотеки. І
результат вашої праці повинен бути сумісний з існуючими програмами
для процесів з одним програмним потоком.