
- •Практичне заняття 8 Організація уведення-виведення в unix. Файли пристроїв. Апаратні переривання
- •Операції над файловими системами. Монтування файлових систем
- •Блокові, символьні пристрої. Поняття драйвера. Блокові, символьні драйвери, драйвери низького рівня. Файловий інтерфейс
- •Апаратні переривання (interrupt), виключення (exception), програмні переривання (trap, software interrupt). Їх обробка
- •Поняття сигналу. Способи виникнення сигналів і види їх обробки
- •Поняття групи процесів, сеансу, лідера групи, лідера сеансу, управляючого терміналу сеансу. Системні виклики getpgrp(), setpgrp(), getpgid(), setpgid(), getsid(), setsid()
- •Системний виклик kill() і команда kill
- •Вивчення особливостей отримання термінальних сигналів поточною і фоновою групою процесів
- •Системний виклик signal(). Установка власного обробника сигналу
- •Прогін програми, що ігнорує сигнал sigint
- •Прогін програми з призначеною для користувача обробкою сигналу sigint
- •Відновлення попередньої реакції на сигнал
- •Сигнали sigusr1 і sigusr2. Використовування сигналів для синхронізації процесів
- •Завершення породженого процесу. Системний виклик waitpid(). Сигнал sigchld
- •Прогін програми для ілюстрації обробки сигналу sigchld
- •Виникнення сигналу sigpipe при спробі запису в pipe або fifo, який ніхто не збирається читати
- •Поняття про надійність сигналів. Posix функції для роботи з сигналами
- •Питання до захисту роботи
Прогін програми для ілюстрації обробки сигналу sigchld
Для закріплення матеріалу розглянемо приклад програми 13—14-5.c з асинхронним отриманням інформації про статус завершення породженого процесу.
/* Програма з асинхронним отриманням інформації про статусі двох породжених процесів, що завершилися */
#include <sys/types.h>
#include <unistd.h>
#include <waith>
#include <signal.h>
#include <stdio.h>
/* Функція my_handler – обробник сигналу SIGCHLD */
void my_handler(int nsig){
int status;
pid_t pid;
/* Опитуємо статус процесу, що завершився, і одночасно взнаємо його ідентифікатор */
if((pid = waitpid(-1 &status, 0)) < 0){
/* Якщо виникла помилка – повідомляємо про неї і продовжуємо роботу */
printf("Some error on waitpid errno = %d\n", errno);
} else {
/* Інакше аналізуємо статус процесу, що завершився */
if ((status & 0xff) == 0) {
/* Процес завершився з явним або неявним викликом функції exit() */
printf("Process %d was exited with status %d\n", pid, status >> 8);
} else if ((status & 0xff00) == 0){
/* Процес був завершений за допомогою сигналу */
printf("Process %d killed signal %d %s\n", pid, status &0x7f,(status & 0x80)? "with core file" : "without core file");
}
}
}
int main(void){
pid_t pid;
/* Встановлюємо обробник для сигналу SIGCHLD */
(void) signal(SIGCHLD, my_handler);
/* Породжуємо Сhild 1 */
if((pid = fork()) < 0){
printf("Can\'t fork child 1\n");
exit(1);
} else if (pid == 0){
/* Child 1 – завершується з кодом 200 */
exit(200);
}
/* Продовження процесу-батька – породжуємо Сhild 2 */
if((pid = fork()) < 0){
printf("Can\'t fork child 2\n");
exit(1);
} else if (pid == 0){
/* Child 2 – циклиться, необхідно видаляти з допомогою
сигналу! */
while(1);
}
/* Продовження процесу-батька – йдемо в цикл */
while(1);
return 0;
}
Лістинг 13-14.5. Програма (13—14-5.c) з асинхронним отриманням інформації про статус двох породжених процесів, що завершилися.
В цій програмі батько породжує два процеси. Один з них завершується з кодом 200, а другий зациклюється. Перед породженням процесів батько встановлює обробник переривання для сигналу SIGCHLD, а після їх породження йде в нескінченний цикл. В обробнику переривання викликається waitpid() для будь-якого породженого процесу. Оскільки в обробник ми потрапляємо, коли який-небудь з процесів завершився, системний виклик не блокується, і ми можемо одержати інформацію про ідентифікатор процесу, що завершився, і причину його завершення. Відкомпілюйте програму і запустіть її на виконання. Другий породжений процес завершуйте за допомогою команди kill з яким-небудь номером сигналу. Батьківський процес також буде необхідний завершувати командою kill.
Виникнення сигналу sigpipe при спробі запису в pipe або fifo, який ніхто не збирається читати
В матеріалах заняття 5 (розділ "Особливості поведінки викликів read() і write() для pip'а") при обговоренні роботи з pip'ами і FIFO ми говорили, що для них системні виклики read() і write() мають певні особливості поведінки. Однією з таких особливостей є отримання сигналу SIGPIPE процесом, який намагається записувати інформацію в pipe або в FIFO у тому випадку, коли читати її вже якомусь (немає жодного процесу, який тримає відповідний pipe або FIFO відкритим для читання). Реакція за умовчанням на цей сигнал – припинити роботу процесу. Тепер ми вже можемо написати коректну обробку цього сигналу користувачем, наприклад, для елегантного припинення роботи пишучого процесу. Проте для повноти картини необхідно познайомитися з особливостями поведінки деяких системних викликів при отриманні процесом сигналів під час їх виконання.
По ходу нашого курсу ми представили ряд системних викликів, які можуть під час виконання блокувати процес. До їх числа відносяться системний виклик open() при відкритті FIFO, системні виклики read() і write() при роботі з pip'ами і FIFO, системні виклики msgsnd() і msgrcv() при роботі з чергами повідомлень, системний виклик semop() при роботі з семафорами і т.д. Що відбудеться з процесом, якщо він, виконуючи один з цих системних викликів, одержить який-небудь сигнал? Подальша поведінка процесу залежить від встановленої для нього реакції на цей сигнал.
Якщо реакція на одержаний сигнал була "ігнорувати сигнал" (незалежно від того, встановлена вона за умовчанням або користувачем за допомогою системного виклику signal()), то поведінка процесу не зміниться.
Якщо реакція на одержаний сигнал встановлена за умовчанням і полягає в припиненні роботи процесу, то процес перейде в стан закінчив виконання.
Якщо реакція процесу на сигнал полягає у виконанні призначеної для користувача функції, то процес виконає цю функцію (якщо він знаходився в змозі очікування, він потрапить в стан готовність і потім в стан виконання) і повернеться з системного виклику з констатацією помилкової ситуації (деякі системні виклики дозволяють операційній системі після виконання обробки сигналу знов повернути процес в стан очікування). Відрізнити таке повернення від дійсно помилкової ситуації можна за допомогою значення системної змінної errno, яка в цьому випадку прийме значення EINTR (для виклику write і сигналу SIGPIPE відповідне значення як виняток буде EPIPE).
Після цього короткого обговорення стає до кінця ясно, як коректно обробити ситуацію "ніхто не хотів прочитати" для системного виклику write(). Щоб сигнал SIGPIPE, що прийшов, не завершив роботу нашого процесу за умовчанням, ми повинні його обробити самостійно (функція-обробник при цьому може бути і порожній!). Але цього мало. Оскільки нормальний хід виконання системного виклику був порушений сигналом, ми повернемося з нього з негативним значенням, яке свідчить про помилку. Проаналізувавши значення системної змінної errno на предмет збігу із значенням EPIPE, ми можемо відрізнити виникнення сигналу SIGPIPE від інших помилкових ситуацій (неправильні значення параметрів і т.д.) і граціозно продовжити роботу програми.