Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекція № 4_МЗКІТ (10 ТП).docx
Скачиваний:
1
Добавлен:
01.07.2025
Размер:
2 Мб
Скачать
☆

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( ), то це призведе до вилучення інформації. про процес-нащадок з таблиці процесів, внаслідок чого зомбі в системі не залишиться.