Програмування обробників сигналів

 

Мета роботи. Вивчити методи розробки програм для обробки сигналів.

 

Сучасні операційні системи підтримують механізм обробки сигналів. Сигнал - це програмний засіб, що забезпечує реакцію системи на зовнішню подію. В якості таких подій можуть виступати: переривання програми по Ctrl+C, помилка виконання операцій з плаваючою точкою, порушення захисту пам'яті, неприпустимий код інструкції.

Реакція системи полягає або в ігноруванні сигналу, або в завершенні роботи програми, або у відновленні стандартної реакції системи, або у виклику наперед визначеної функції. Стандартна бібліотека Сі надає користувачу ряд функцій, що забезпечують підтримку сигнального механізму.

Основна функція - це

 

void (* signal(int sig, void (*func)(/*int */))) (int)

 

де сигнал sig повинен бути однією з явних констант, що визначені в signal.h. Наведемо деякі з них.

SIGINT відповідає сигналу переривання ОС INT23H по натисненні користувачем комбінації клавіш Ctrl+C.

SIGFPE відповідає винятковим ситуаціям з плаваючою крапкою, таким як переповнення, ділення на нуль, недійсна операція.

Сигнал SIGABRT генерується при ненормальному завершенні програми, наприклад, за допомогою функції abort.

Аргумент func визначає спосіб реакції системи на сигнал. Він повинен бути або однією з явних констант SIG_DFL або SIG_IGN, визначених в signal.h, або адресою функції. Дія, яка вибирається, коли сигнал переривання був отриманий, залежить від значення func таким чином:

При SIG_IGN сигнал переривання ігнорується. Це значення ніколи не повинне задаватися для SIGFPE, оскільки стан процесора з плаваючою крапкою стає невизначеним.

При SIG_DFL відновлюється стандартна реакція системи на сигнал.

Якщо була задана адреса функції, то при виникненні сигналу sig відбувається негайна передача управління специфікованій функції. Якщо функція повертає управління, викликаний процес відновлює виконання безпосередньо за точкою, в якій був прийнятий сигнал переривання і система відновлює стандартну реакцію на даний сигнал. Тому, для повторного забезпечення необхідної реакції на сигнал, користувач повинен знову викликати функцію signal.

Функція funс повинна мати тип void.

Функція signal повертає попереднє значення аргументу func, тобто типом значення, що повертається, є вказівник на функцію. При невдалому завершенні значенням функції буде константа SIG_ERR, що визначається як вказівник на функцію:

#define SIG_ERR ((void (* _Cdecl)(int))-1).

 

Пам'ятайте, що функція signal не виконує видимих для користувача дій. Вона тільки визначає реакцію системи на сигнал.

Наведена нижче програма здійснює безперервне циклічне виведення послідовностей десяткових цифр. При натисненні комбінації клавіш Ctrl+C, управління передається функції handler, яка або завершує програму, або визначає себе як обробник сигналу SIGINT. Після закінчення функція передає управління в перервану точку функції main. Запустіть цю програму на виконання. Чітко набирайте на клавіатурі комбінацію Ctrl+C. Зверніть увагу як функція handler повертає управління. При цьому, в послідовності десяткових цифр немає пропусків.

Приклад.

 

#include <stdio.h>

#include <signal.h>

#include <process.h>

void handler(void);/* Обробник переривання*/

main( )

{char ch; int i;

if (signal(SIGINT, handler)==SIG_ERR)

{реrror("Неможливо встановити нову реакцію на Ctrl+C");

exit(1); }

printf("\n^C - вхід в програму обробки сигналу, інший символ - цикл”);

scanf("%1c",&ch);

while(1)for(i=0;i<10;i++)printf("%d\n",i);

exit(0);

}

void handler( )

{char ch;

printf("\nПpогpама обробки переривання\

Вихід Y/у, інакше повернення в цикл.");

scanf("%1c",&ch);

if (ch == 'y' || ch == 'Y') exit(0);

signal (SIGINT,handler)

}

При програмуванні обробників сигналів широко використовують функції longjmp і setjmp, призначені для нелокального переходу між функціями, без звичайних послідовностей виклику процедур і повернення з них.

Функція int setjmp(jmp_buf env) зберігає в структурі env обчислювальне середовище процедури, з якої вона була викликана. Це середовище може бути надалі поновлене за допомогою функції void longjmp(jmp_buf env, int value).

Шаблон структури jmp_buf, об’явлений в заголовному файлі setjmp.h. Виклик longjmp повертає управління в точку, наступну безпосередньо за відповідним викликом функції setjmp. Виконання продовжується таким же чином, наче функція setjmp повернула значення value, визначене у функції longjmp. Значення всіх змінних, за винятком регістрових зберігаються. Якщо ж функція setjmp викликається звичайним чином, то вона повертає нуль.

Приклад

 

#include <stdio.h>

#include <setjmp.h>

jmp_buf mark;

void p(void);

main( )

{if (setjmp(mark) !=0)

{printf("longjmp був викликаний.\n");

exit(1); }

printf("setjmp був викликаний.\n");

p( );}

void p( )

{printf("Увійшли до функції.\n");

longjmp(mark, -1);

printf("Вийшли з функції.\n");

}

На екран дисплея буде видана наступна послідовність рядків:

setjmp був викликаний.

Увійшли до функції.

longjmp був викликаний.

Повідомлення "Вийшли з функції." надруковано не буде.

Функції нелокального переходу дозволяють організувати виконання двох функцій як співпрограм. Техніка співпрограм широко застосовується в системному програмуванні для організації паралельних процесів.

Суть співпрограм пояснемо на наступному прикладі.

 

Процедура А Процедура Б

 

Оператор_а0 Оператор_б0

Викликати Б Відновити А

Оператор_а1 Оператор_б1

Відновити Б Відновити А

Оператор_а2 Оператор_б2

Відновити Б Відновити А

Оператор_а3

Кінець

Якщо першою виконується процедура А, то має місце наступна траса виконання: Оператор_а0, Оператор_б0, Оператор_а1, оператор_б1, Оператор_а2, Оператор_б2, Оператор_а3.

Реалізуємо наведений приклад за допомогою функцій setjmp і longjmp.

 

#include <stdio.h>

#include <setjmp.h>

void f(void);

jmp_buf m,m1;

main() /*Програма А*/

{printf("Оператор_а0\n ");

if(!setjmp(m)) f(); /*Викликати Б*/

printf("Оператор_а1\n ");

if(!setjmp(m))longjmp(m1,1); /*Відновити Б*/

printf("Оператор_а2\n ");

if(!setjmp(m))longjmp(m1,1); /*Відновити Б*/

printf("Оператор_а3\n ");

} /*Кінець*/

void f() /*Програма Б*/

{ printf("Оператор_б0\n ");

if(!setjmp(m1))longjmp(m,1); /*Відновити А*/

printf("Оператор_б1\n ");

if(!setjmp(m1))longjmp(m,1); /*Відновити А*/

printf("Оператор_б2\n ");

if(!setjmp(m1))longjmp(m,1); /*Відновити А*/

}

 

На екран буде виведена вищевказана траса виконання.

 

Практична частина.

 

1. Вивчити принципи програмування сигналів і організацію нелокальних переходів. Вміти відповідати на контрольні питання.

2. Скласти програму, що забезпечує виведення інформації в файл в діалоговому режимі і виконуючу наступні дії. Програма видає запит: «Введіть рядок для запису у файл або Сtrl+C для інших варіантів:»

В звичайному режимі програма пише рядок у файл. При натисненні

Ctrl+C, програма повинна надавати користувачу наступний набір варіантів, що пропонується і реалізується функцією обробника сигналу SIGINT:

С - продовження введення з клавіатури і виведення у файл з перерваної точки;

Q - вихід в ОС;

W - Завершення введення-виведення і продовження програми.

При виборі пункту С програма знову пропонує: «Введіть рядок для запису у файл або Сtrl+C для інших варіантів:»

Якщо користувач вирішив продовжити програму, то виведення в файл припиняється і програма продовжує працювати далі, наприклад, виведе числа від 0 до 100.

Для реалізації завдання Вам рекомендується наступна конструкція

#include<stdio.h>

#include<setjmp.h>

jmp_buf m;

main()

{<Відкриття файлу для запису.>

<Установка функції f як обробника сигналу SIGINT.>

if(!setjmp(m))/*Зберігання середовища для подальшого нелокального переходу*/

{while(1)

{<Введення рядків з терміналу і їх виведення у файл. Виведення на магнітний носій не повино бути перервано. Тому на період запису чергового рядка необхідно заборонити реакцію системи на сигнал SIGINT. Після запису кожного рядка слід відновлювати бажану реакцію f на сигнал SIGINT.>

}

 

else <Продовження роботи програми.>

}

void f()

{<Виведення на екран варіантів С, Q і W.>

<Обробка кожного з них:

{case 'C': <Повернення в точку переривання.>

case 'Q': <Закриття файлу і завершення програми.>

case'W': <Нелокальний перехід в потрібну точку функції main.>

}

<Після кожного виклику функції обробника сигналу SIGINT не забувайте відновлювати реакцію системи на цей сигнал.>

}

Контрольні питання.

 

1. Що таке сигнал?

2. Які події в системі можуть стати причиною виникнення сигналу?

3. Як система може реагувати на сигнал?

4. Які сигнали можна обробити, використовуючи стандартну бібліотеку мови Сі?

5. Який тип має значення, що повертається функцією signal?

6. Як встановити потрібну функцію як обробника сигналу?

7. Якщо сигнал виникає кілька разів підряд, то скільки разів треба викликати функцію sigmal для визначення бажаної реакції на даний сигнал?

8. Куди повертається управління після завершення функції обробника сигналу?

9. Що таке нелокальний перехід між функціями?

10. Які функції підтримують механізм нелокальних переходів між функціями?

11. Куди повертає управління функція longjmp?

12. Як визначити значення, яке повертає функція setjmp?

13. Як організувати механізм співпрограм, використовуючи функції нелокального переходу?

14. Поясніть роботу програми, що моделює роботу співпрограм.

 


Список рекомендованої літератури

 

1. З.Я. Шпак Програмування мовою С. – Львів: Оріяна-Нова, 2006. - 412 с.

2. Харт Джонсон М. Системное программирование в среде Win32. – М.: Издательский дом “Вильямс”, 2001.

3. Керниган Б., Ритчи Д. Язык программирования Си. – СПб-Киев, 2006. – 272 с.

4. Шилдт Г. Полный справочник по С. – М.: Издательский дом “Вильямс”, 2002. - 704 с.

 

ЗМІСТ

 

Вступ 3

Лабораторна робота №1. Запуск файлів, що виконуються, з програми. 4

Лабораторна робота №2. Робота з динамічними структурами даних. Списки. 9

Лабораторна робота №3. Прямий доступ в потоках. 16

Лабораторна робота №4. Перевизначення файлів та потоків. 26

Лабораторна робота №5. Файли з довільною виборкою. 31

Лабораторна робота №6. Програмування обробників сигналів. 42

Список рекомендованої літератури 48