Системний виклик waitpid()
Для вилучення із системи інформації про процес в Linux можна використати системний виклик wait(), але частіше застосовують його більш універсальний варіант – waitpid(). Цей виклик перевіряє, чи є керуючий блок відповіжного процесу в системі. Якщо він є, а прцес не перебуває у стані зомби (тобто ще виконується), то процес у разі виклику waitpid() переходить у стан очікування. Коли ж процес-нащадок завершується, предок виходить зі стану очікування і вилучає керуючий блок нащадка. Якщо предок не викличе waitpid() для нащадка, той може залишитися у стані зомби надовго.
Синхронне виконання процесів у прикладних програмах
Розглянемо синхронне виконання процесів на базі waitpid(). Відповідно до POSIX цей системний виклик визначається так:
#include<sys/wait.h>
pid_t waitpid(pid_t pid, //pid процесу, який очікуємо
int *status, //інформація про статус завершення нащадка
int options); //задавитемо як 0
Параметр pid можна задавати як 0, що означатиме очікування процесу і з тієї ж групи, до якої належить предок, або як –1, що означатиме очікування будь-якого нащадка. Наведемо приклад реалізації синхронного виконання з очікуванням:
pid_t pid;
if((pid=fork())==-1)
exit(-1);
if(pid==0)
{
//нащадок – виклик exec()
}
else
{
//предок – чекати нащадка
int status;
waitpid(pid,&status,0);
//продовжувати виконання
}
Зі значення status можна отримати додаткову інформацію про процес-нащадок, що завершився. Для цього є низка макропідстановок з <sys/wait.h>:
· WIFEXITED(status) – ненульове значення, коли нащадок гащадок завершився нормально;
· WEXITSTATUS(status) – код повернення нащадка (тільки коли WIFEXITED()!=0).
Код повернення нащадка отриманий таким чином:
waitpid(pid,&status,0);
if(WIFEXITED(status))
printf(“Нащадок завершився з кодом %d\n”,
WEXITSTATUS(status));
Сигнали
За умов багатозадачності виникає необхідністьсповіщати процеси про події, що відбуваються в системі або інших процесах. Найпростішим механізмом такого сповіщання, визначеним POSIX, є сигнали. Процес після отримання сигналу негайно реагує на нього викликом спеціальної функції – оброблювача цього сигналу (signal handler), або виконанням дії за замовчуванням для цього сигналу. Із кожним сигналом пов’язаний його номер, що є унікальним у системі. Жодної іншої інформації разом із сигналом передано бути не може.
Сигнали є найпростішим механізмом міжпроцесової взаємодії в UNIX-системах, але, оскільки з їхньою допомогою можна передавати обмежені дані, вони переважно використовуються для повідомлення про події.
Типи сигналів
Залежго від обстави виникнення сигнали поділяють на синхронні й асинхронні. Синхронні сигнали виникають під час виконання активного потоку процесу (зазвичай чрез помилку – доступ до невірної ділянки пам’яті, некоректну роботу із плаваючою крапкою, виконання неправильної інструкції процесора). Ці сигнали генерує ядро ОС і негайно
відправляє їх процесу, потік якого викликав помилку.
Асинхронні сигнали процес може отримувати у будь-який момент виконання:
· програміст може надіслати асинхронний сигнал іншому процесу, використовуючи системний виклик, який у POSIX-системах називають kill(), параметрами цього виклику є номер сигналу та ідентифікатор процесу;
· причиною виникнення сигналу може бути також деяка зовнішня подія (натискання користувача на клавіші, завершення процесу-нащадка тощо).
Під час обробки таких сигналів система має перервати виконання поточного коду, виконати код оброблювача і повернутися до тієї ж самої інструкції, що виконувалась у момент отримання сигналу.
Диспозиція сигналів
На надходження сигналу процес може реагувати одним із трьох срособів (спосіб реакції процесу на сигнал називають диспозицією сигналу):
· викликати оброблювач сигналу;
· проігнорувати сигнал, який у цьому випадку “зникне” і не виконає жодної дії;
· використати диспозицію, перебачену за замовчуванням (така диспозиція задана для кожного сигналу, найчастіше це – завершення процесу).
Процес може задавати диспзицію для кожного сигналу окремо.
Блокування сигналів
Процес може не лише задавати диспозицію сигналів, а й визначати свою готовність у цей момент приймати сигнали певного типу. Якщо процес не готовий, він може ці сигнализаблокувати. Якщо заблокований сигнал має
бути доставлений процесу,система ставить його в чергу, де він залишатиметься доти, поки процес його не розблокує.Процес блокує і роблоковує сигнали шляхом зміни маски сигналів процесу (спеціальної структури даних, де зберігається інформація про те, які сигнали можуть бути негайно доставлені процесу, зазвичай вона зберігається в його керуючому блоці. Процеси-нащадки успадковують маску сигналів предка.
Приклади сигналів
Розглянемо сигнали, що визначені POSIX і підтримуються в Linux (у дужках поруч з ім’ям сигналу наведено його номер).
До синхронних сигналів належить, наприклад, сигнал SIGSEGV (11), який генерує система під час записування в захищену ділянку пам’яті.
До асинхронних сигналів належать:
· SIGHUP (1) – розрив зв’язку (наприклад, вихід користувача із системи);
· SIGINT і SIGQUIT (2,3) – сигнали переривання програми від клавіатури (генеруються під час натискання користувачем відповідно Ctrl+C і Ctrl+\);
· SIGKILL (9) – негайне припинення роботи програми (для такого сигналу не можна змінювати диспозицію);
· SIGUSR1 SIGUSR2 (10,12) – сигнали користувача, які можуть використовувати прикладні програми;
· SIGTERM (15) – пропозиція програмі завершити її роботу (цей сигнал, на відміну від SIGKILL, може бути зігнорований).
Діями за замовчуванням для всіх зазначених сигналів, крім SIGSEGV, є припинення роботи програми (для SIGSEGV додатково генерується дамп пам’яті (core dump) – файл, у якому зберігається образ адресного простору процесу для наступного аналізу).
Задання диспозиції сигналів
Для за дання диспозиції сигналу використовують системний виклик
sigaction().
#include<signal.h>
int sigaction(int signum, //номер сигналу
struct sigaction *action, //нова диспозиція
struct sigaction *old_action); //повернення попередньої диспозиції
Диспозицію описують за допомогою структури sigaction з такими полями:
· sa_handler – покажчик на функцію-оброблювач сигналу;
· sa_mask – маска сигналу, що задає, які сигнали будуть заблоковані всередені оброблювача;
· sa_flag – додаткові прапорці.
Обмежимося обнулінням sa_mask і sa_flag (не блокуючи жодного сигналу):
struct sigaction action={0};
Поле sa_handler має бути задане як покажчик на оголошену раніше функцію, що має такий вигляд:
void user_handler(int signum)
{
//обробка сигналу
}
Ця функція і стає оброблювачем сигналів.
Параметр signum визначає, який сигнал надійшов в оброблювач (той самий оброблювач може бути зареєстрований для декількох сигналів кількома викликами sigaction()).
Після реєстрації оброблювач викликатиметься завжди, коли надійде
відповідний сигнал:
#include<signal.h>
void sigint_handler(int signum)
{
//обробка SIGINT
}
//……..
action.sa_handler=sigint_handler;
sigaction(SIGINT, &action,0);
Якщо нам потрібно організувати очікування сигналу, то найпрстішим способом є викристання системного виклику pause(). У цьому разі процес переходить у режім очікування , з якого його виведе поява будь-якого сигналу:
//задати оброблювачі за допомого за допомогою sigaction()
pause(); //чекати сигналу
Генерування сигналів
Розглянемо, як надіслати сигнал процесу. Для цього використовується системний виклик kill().
#include(signal.h>
int kill(pid_t pid, //ідентификатор процесу
int signum); //номер сигналу
Наприклад, надсилання сигналу SIGHUP процесу, pidякого задано у командному рядку:
kill(atoi(argv[1],SIGHUP);
Оргагізація асинхронного виконання процесів
Розглянемо, як можна використати обробку сигналів для організації асинхронного виконання процесів. Як відомо, виклик waitpid() спричиняє організацію синхронного виконання нащадка. Коли необхідно запустити
процес-нащадок асинхронно, то здається природним не викликати в процесі-предку waitpid()для цього нащадка. Але після завершення процесу-нащадка він перетвориться на процес-зомбі.
Щоб цього уникнути, необхідно скористатися тим, що під час завершення процесу-нащадка процес-предок отримує спеціальний сигнал SIGCHLD. Якщо в оброблювачі цього сигналу викликати waitpid(), то це призведе до вилучення інформації про процес-нащадок з таблиці-процесів, внаслідок чого зомбі в системі не залишиться.
void clear_zombie(int signum)
{
waitpid(-1,NULL,0);
}
struct sigaction action={0};
action.sa_handler=clear_zombie;
sigaction(SIGCHLD,&action,0);
if((pid=fork())==-1)
_exit();
if(pid==0)
{
// нащадок запущений асинхронно
exit();
}
else
{
// предок не має виклику waitpid()
}