Системний виклик 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()

}