- •9.3. Створення процесів
- •Базові поняття процесів і потоків
- •1.1. Процеси і потоки в сучасних ос
- •1.2. Моделі процесів і потоків
- •1.3. Складові елементи процесів і потоків
- •2. Багатопотоковість та її реалізація
- •2.1. Поняття паралелізму
- •2.2. Види паралелізму
- •2.4. Способи реалізації моделі потоків
- •2.3. Переваги і недоліки багато потоковості
- •3. Стани процесів і потоків
- •4. Опис процесів і потоків
- •4.1. Керуючі блоки процесів і потоків
- •4.2. Образи процесу і потоку
- •5. Перемикання контексту й обробка переривань
- •5.1. Організація перемикання контексту
- •5.2. Обробка переривань
- •6. Створення і завершення процесів і потоків
- •6.1. Створення процесів
- •6.2. Ієрархія процесів
- •6.3. Керування адресним простором під час створення процесів
- •6.4. Особливості завершення процесів
- •6.5. Синхронне й асинхронне виконання процесів
- •6.6. Створення і завершення потоків
- •7. Керування процесами в unix і Linux
- •7.1. Образ процесу
- •7.2. Ідентифікаційна інформація та атрибути безпеки процесу
- •7.3. Керуючий блок процесу
- •7.5. Завершення процесу
- •7.7. Запуск програми
- •7.8. Очікування завершення процесу
- •7.9. Сигнали
- •8. Керування потоками в Linux
- •8.1. Базова підтримка багато потоковості
- •8.2. Особливості нової реалізації багато потоковості в ядрі Linux
- •8.3. Потоки ядра Linux
- •8.4. Програмний інтерфейс керування потоками posix
- •9. Керування процесами у Windows хр
- •9.1. Складові елементи процесу
- •9.2. Структури даних процесу
- •9.3. Створення процесів
- •9.4. Завершення процесів
- •9.5. Процеси і ресурси. Таблиця об’єктів процесу
- •9.6. Програмний інтерфейс керування процесами Win32 арі
- •10. Керування потоками у Windows хр
- •10.1. Складові елементи потоку
- •10.2. Структури даних потоку
- •10.3. Створення потоків
- •10.4. Особливості програмного інтерфейсу потоків
- •Висновки
- •Контрольні запитання та завдання
7.8. Очікування завершення процесу
Коли процес завершується, його керуючий блок не вилучається зі списку
й хеша процесів негайно, а залишається там доти, поки інший процес (предок) не видалить його звідти. Якщо процес насправді в системі відсутній (він завершений), а є тільки його керуючий блок, то такий процес називають процесом-зомбі (zombie process).
Системний виклик waitpid( )
Для вилучення із системи інформації про процес в Linux можна використати описаний вище системний виклик wait ( ), але частіше застосовують його більш універсальний варіант — waitpid( ). Цей виклик перевіряє, чи є керуючий блок відповідного процесу в системі. Якщо він є, а процес не перебуває у стані зомбі (тобто ще виконується), то процес у разі виклику waitpid( ) переходить у стан очікування. Коли ж процес-нащадок завершується, предок виходить зі стану очікування і вилучає керуючий блок нащадка. Якщо предок не викличе
waitpid( ) для нащадка, той може залишитися у стані зомбі надовго.
Синхронне виконання процесів у прикладних програмах
Розглянемо реалізацію синхронного виконання процесів на базі waitpid( ). Відповідно до POSIX цей системний виклик визначається так:
Параметр pid можна задавати як 0, що означатиме очікування процесу із тієї ж групи, до якої належить предок, або як -1, що означатиме очікування будь-якого нащадка. Наведемо приклад реалізації синхронного виконання з очікуванням:
Зі значення status можна отримати додаткову інформацію про процес-нащадок, що завершився. Для цього є низка макропідстановок з <sys/wait.h>:
♦ WIFEXITED(status) — ненульове значення, коли нащадок завершився нормально;
♦ WEXITSTATUS(status) - код повернення нащадка (тільки коли WIFEXITED( )=0).
Код повернення нащадка отриманий таким чином:
Асинхронне виконання процесів може бути реалізоване на основі сигналів і буде розглянуте в наступному розділі.
7.9. Сигнали
За умов багатозадачності виникає необхідність сповіщати процеси про події, що відбуваються в системі або інших процесах. Найпростішим механізмом такого сповіщення, визначеним POSIX, є сигнали. Сигнали діють на кшталт програмних переривань: процес після отримання сигналу негайно реагує на нього викликом спеціальної функції — оброблювача цього сигналу (signal handler) або виконанням дії за замовчуванням для цього сигналу. Із кожним сигналом пов'язаний його номер, що є унікальним у системі. Жодної іншої інформації разом із сигналом передано бути не може.
Сигнали є найпростішим механізмом міжпроцесової взаємодії в UNIX-системах, але, оскільки з їхньою допомогою можна передавати обмежені дані, вони переважно використовуються для повідомлення про події.
Типи сигналів
Залежно від обставин виникнення сигнали поділяють на сшіхронні й асинхронні.
Синхронні сигнали виникають під час виконання активного потоку процесу (зазвичай через помилку - доступ до невірної ділянки пам’яті, некоректну роботу із плаваючою крапкою, виконання неправильної інструкції процесора). Ці сигнали генерує ядро ОС і негайно відправляє їх процесу, потік якого викликав помилку.
Асинхронні сигнали процес може отримувати у будь-який момент виконання:
♦ програміст може надіслати асинхронний сигнал іншому процесу, використовуючи системний виклик, який у POSIX-системах називають kill ( ), параметрами цього виклику є номер сигналу та ідентифікатор процесу;
♦ причиною виникнення сигналу може бути також деяка зовнішня подія (натискання користувача на певні клавіші, завершення процесу-нащадка тощо).
Під час обробки таких сигналів система має перервати виконання поточного коду, виконати код оброблювача і повернутися до тієї ж самої інструкції, що виконувалась у момент отримання сигналу.
Диспозиція сигналів
На надходження сигналу процес може реагувати одним із трьох способів (спосіб реакції процесу на сигнал називають диспозицією сигналу):
♦ викликати оброблювач сигналу;
♦ проігнорувати сигнал, який у цьому випадку «зникне» і не виконає жодної дії; ♦ використати диспозицію, передбачену за замовчуванням (така диспозиція задана для кожного сигналу, найчастіше це — завершення процесу).
Процес може задавати диспозицію для кожного сигналу окремо.
Блокування сигналів
Процес може не лише задавати диспозицію сигналів, а й визначати свою готовність у цей момент приймати сигнали певного типу. Якщо процес не готовий, він може ці сигнали заблокувати. Якщо заблокований сигнал має бути доставлений процесу, система ставить його в чергу, де він залишатиметься доти, поки процес його не розблокує. Процес блокує й розблоковує сигнали шляхом зміни маски сигналів процесу (спеціальної структури даних, де зберігається інформація про те, які сигнали можуть бути негайно доставлені процесу, а які в цей момент заблоковані).
Маска сигналів є характеристикою процесу, зазвичай вона зберігається в його керуючому блоці. Процеси-нащадки успадковують маску сигналів предка.
Приклади сигналів
Назвемо сигнали, що визначені POSIX і підтримуються в Linux (у дужках поруч
з ім’ям сигналу наведено його номер).
До синхронних сигналів належить, наприклад, сигнал SIGSEGV (11), який генерує система під час записування в захищену ділянку пам’яті.
До асинхронних сигналів належать:
♦ SIGHUP (1) — розрив зв’язку (наприклад, вихід користувача із системи);
♦ SIGINT і SIGQUIT (2,3) — сигнали переривання програми від клавіатури (генеруються під час натискання користувачем відповідно Ctri+C і Ctrl+\);
♦ SIGKILL (9) - негайне припинення роботи програми (для такого сигналу не можна змінювати диспозицію);
♦ SIGUSR1 і SIGUSR2 (10,12) — сигнали користувача, які можуть використовувати прикладні програми;
♦ SIGTERM (15) — пропозиція програмі завершити її роботу (цей сигнал, на відміну від SIGKILL, може бути зігнорований).
Діями за замовчуванням для всіх зазначених сигналів, крім SIGSEGV, є припинення роботи програми (для SIGSEGV додатково генерується дамп пам’яті (core dump) — файл, у якому зберігається образ адресного простору процесу для наступного аналізу).
Задання диспозиції сигналів
Для задання диспозиції сигналу використовують системний виклик sigaction( ).
Диспозицію описують за допомогою структури sigaction з такими полями:
♦ sa_handler — покажчик на функцію-оброблювач сигналу;
♦ sa_mask — маска сигналу, що задає, які сигнали будуть заблоковані всередині оброблювача;
♦ sa_flag - додаткові прапорці.
Обмежимося обнулінням sa_mask і sa_flag (не блокуючи жодного сигналу):
Поле sa_handler має бути задане як покажчик на оголошену раніше функцію, що має такий вигляд:
Ця функція і стає оброблювачем сигналів.
Параметр signum визначає, який сигнал надійшов в оброблювач (той самий оброблювач може бути зареєстрований для декількох сигналів кількома викликами sigaction( )).
Після реєстрації оброблювач викликатиметься завжди, коли надійде відповідний сигнал:
Якщо нам потрібно організувати очікування сигналу, то найпростішим способом є використання системного виклику pause( ). У цьому разі процес переходить у режим очікування, з якого його виведе поява будь-якого сигналу:
Генерування сигналів
Тепер зупинимося на тому, як надіслати сигнал процесу. Для цього використовують системний виклик кіll ( ).
Наведемо приклад надсилання SIGHUP процесу, pid якого задано у командному рядку:
Організація асинхронного виконання процесів
Розглянемо, як можна використати обробку сигналів для організації асинхронного виконання процесів. Як відомо, виклик waitpid( ) спричиняє організацію синхронного виконання нащадка. Коли необхідно запустити процес-нащадок асинхронно, то здається природним не викликати в процесі-предку waitpid( ) для цього нащадка. Але після завершення процесу-нащадка він перетвориться на процес-зомбі.
Щоб цього уникнути, необхідно скористатися тим, що під час завершення процесу-нащадка процес-предок отримує спеціальний сигнал SIGCHLD. Якщо в оброблювачі цього сигналу викликати waitpid( ), то це призведе до вилучення інформації. про процес-нащадок з таблиці процесів, внаслідок чого зомбі в системі не залишиться.
