Сигнали та переривання в системі UNIX

У цьому розділі поетапно розглянемо процес обробки сигналів (таких як переривання), що надходять із зовнішнього світу, а також помилок програми Помилки програм виникають в основному через непра-

Вільних звернень до памяті, при виконанні специфічних інстр рукцій або через операцій з плаваючою крапкою Найбільш поширені сигнали, що надходять із зовнішнього світу:переривання (interrupt) – Цей сигнал посилається, коли ви натискаєте клавішу DEL  вихід (quit) – Породжується символом FS (ctl-\)  відключення (hangup) – Викликаний тим, що повішена телефонна трубка, і завершення (terminate) – Породжується командою kill Коли відбувається одне з вищевказаних подій, сигнал посилається всім процесам, запу щенним з даного терміналу, і якщо не існує угод, перед ня записують інше, сигнал завершує процес Для більшості сигналів створюється дамп памяті, який може знадобитися для налагодження (Див adb (1) і sdb (l))

Системний виклик signal змінює дію, що виконується за замовчуванням Він має два аргументи: перший – це номер, який визначає сигнал, другий – це або адреса функції, або ж код, що приписує ігнорувати сигнал або відновлювати дії за умовчанням Файл містить описи різних аргументів Так,

#include &ltsignalh&gt

signal(SIGINT, SIG_IGN)

призводить до ігнорування переривання, в той час як

signal(SIGINT, SIG_DFL)

відновлює дію за замовчуванням – завершення процесу У всіх випадках signal повертає попереднє значення сигналу Якщо другий аргумент – це імя функції (яка повинна бути оголошена в цьому ж вихідному файлі), то вона буде викликана при виникненні сигналу Найчастіше ця можливість використовується для того, щоб дозволити програмі підготуватися до виходу, наприклад видалити тимчасовий файл:

#include &ltsignalh&gt

char  *tempfile  = &quottempXXXXXX"

main()

{

extern onintr()

if  (signal(SIGINT,  SIG_IGN)  =  SIG_IGN) signal(SIGINT,  onintr)

mktemp(tempfile)

/ * Обробка .. * / exit (0)

}

onintr () / * очистити у разі переривання * /

{

unlink(tempfile) exit(1)

}

Навіщо потрібні перевірка і повторний виклик signal в main Згадайте, що сигнали посилаються під всі процеси, запущені на даному терміналі Відповідно, коли програма запущена не в інтерактивному режимі (а за допомогою &), командний процесор дозволяє їй игно рировать переривання, таким чином, програма не буде зупинена перериваннями, призначеними для НЕ фонових процесів Якщо ж програма починається з анонсування того, що всі переривання повинні бути надіслані в onintr, незважаючи ні на що, це зводить нанівець спроби командного процесора захистити програму, що працює у фоновому режимі

Рішення, представлене вище, дозволяє перевірити стан управління переривань і продовжувати ігнорувати переривання, якщо вони ігнорувалися раніше Код враховує той факт, що signal повертає попереднє стан конкретного сигналу І якщо сигнали раніше ігнорувалися, процес буде і далі їх ігнорувати в іншому випадку вони повинні бути перехоплені

У більш складній програмі може знадобитися перехопити переривання та інтерпретувати його як запит на скасування виконуваної операції і повернення в її власний цикл обробки команд Візьмемо, наприклад, текстовий редактор: переривання занадто довгою друку не повинно призводити до виходу з програми і втрати всієї зробленої роботи У цій ситуації можна написати такий код:

#include &ltsignalh&gt

#include  &ltsetjmph&gt jmp_buf  sjbuf

main()

{

int onintr()

if  (signal(SIGINT,  SIG_IGN)  =  SIG_IGN) signal(SIGINT,  onintr)

setjmp (sjbuf) / * Збереження поточної позиції в стеку * / for ( ) {

/ * Основний цикл обробки * /

}

}

onintr () / * перевстановити у разі переривання * /

{

signal (SIGINT, onintr) / * Перевстановити для наступного переривання * / printf (\ nInterrupt \ n)

longjmp (sjbuf, 0) / * Повернення в збережений стан * /}

Файл setjmph оголошує тип jmp_buf як обєкт, в якому може зберігатися положення стека sjbuf оголошується як обєкт такого типу Функція setjmp (3) зберігає запис про місце виконання програми Значення змінних не зберігаються Коли відбувається переривання, ініціюється звернення до програми onintr, яка може надрукувати повідомлення, встановити прапори або зробити що-небудь інше Функція longjmp отримує обєкт, збережений у setjmp, і повертає управління в точку програми, наступну за викликом setjmp Таким чином, управління (і положення стека) повертаються до того місця основної програми, де відбувається вхід в основний цикл

Зверніть увагу на те, що сигнал знову встановлюється в onintr, після того як відбудеться переривання Це необхідно, тому що сигнали при їх отриманні автоматично відновлюють дію за замовчуванням

Деякі програми просто не можуть бути зупинені у довільному місці, наприклад в процесі обробки складної структури даних, тому необхідно мати можливість виявляти сигнали Можливо наступне рішення – треба зробити так, щоб програма обробки переривань встановила прапор і повернулася назад замість того, щоб викликати exit або longjmp Виконання буде продовжено з того самого місця, в якому воно було перервано, а прапор переривання може бути перевірений пізніше

З таким підходом повязана одна трудність Припустимо, що програма читає з терміналу в той час, коли послано переривання Належним чином викликається зазначена підпрограма, яка встановлює прапори і повертається назад Якби справа дійсно йшла так, як було зазначено вище, тобто виконання програми відновлювалося би «з того самого місця, де воно було перервано», то програма мала б продовжувати читати з терміналу до тих пір, поки користувач не надрукував би новий рядок Така поведінка може збивати з пантелику, адже користувач може і не знати, що програма читає, і він, ймовірно, вважав за краще б, щоб сигнал вступав в силу негайно Щоб вирішити цю проблему, система завершує читання, але зі статусом помилки, який вказує, що сталося errno встановлюється в EINTR, визначений у errnoh, щоб позначити перерваний системний виклик

Тому програми, які перехоплюють сигнали і відновлюють роботу після них, повинні бути готові до «помилок», викликаним перерваними системними викликами (Системні виклики, по відношенню до яких треба проявляти обережність, – це читання з термінала, очікування і пауза) Така програма може використовувати код, наведений нижче, для читання стандартного вводу:

#include  &lterrnoh&gt extern  int  errno

if (read (0, & c, 1) <= 0) / * EOF або переривання * /

if (errno == EINTR) {/ * EOF, викликаний перериванням * / errno = 0 / * Перевстановити для наступного разу * /

} Else {/ * справжній кінець файлу * /

}

І остання тонкість, на яку треба звернути увагу, якщо перехоплення сигналів поєднується з виконанням інших програм Припустимо, що програма обробляє переривання і, до того ж, містить метод (Як В ed), за допомогою якого можуть виконуватися інші програми Тоді код буде виглядати приблизно так:

if  (fork()  ==  0) execlp(..)

signal (SIGINT, SIG_IGN) / * Предок ігнорує переривання * /

wait (& status) / * Поки виконується нащадок * / signal (SIGINT, onintr) / * Відновити переривання * /

Чому саме так Сигнали посилаються всім вашим процесам Припустимо, що програма, яку ви викликали, обробляє свої власні переривання, як це робить редактор Якщо ви перериваєте дочірню програму, вона отримає сигнал і повернеться в свій основний цикл, і, ймовірно, прочитає ваш термінал Але зухвала програма також вийде зі стану очікування дочірньої програми і прочитає ваш термінал Наявність двох процесів читання терміналу все заплутує, так як насправді система «підкидає монетку», щоб вирішити, яка програма отримає кожну з рядків вводу Щоб уникнути цього, батьківська програма повинна ігнорувати переривання до тих пір, поки не виконається дочірня Це умовивід відображено в обробці сигналів в system:

#include &ltsignalh&gt

system (s) / * виконати командний рядок s * / char * s

{

int status, pid,  w, tty

int (*istat)(), (*qstat)()

if ((pid  = fork()) == 0)  {

execlp(&quotsh&quot,  &quotsh&quot, &quot–c&quot,  s,  (char *)  0) exit(127)

}

istat =  signal(SIGINT,  SIG_IGN) qstat  =  signal(SIGQUIT,  SIG_IGN)

while  ((w  = wait(&ampstatus))  =  pid  &amp&amp  w   =  –1)

;

if (w ==  –1) status  =  –1

signal(SIGINT,  istat) signal(SIGQUIT,  qstat) return  status

}

У відступ від опису, функція signal очевидно має дещо дивний другий аргумент Насправді це покажчик на функцію, яка повертає ціле число, і це також тип самої функції sig- nal Два значення, SIG_IGN і SIG_DFL, мають правильний тип, але вибі раются таким чином, щоб вони не збігалися ні з якими можливими реальними функціями Для особливо цікавляться наведемо приклад того, як вони описуються для PDP-11 і VAX опису повинні бути досить відразливими, щоб спонукати до використання signalh

#define SIG_DFL  (int (*)())0

#define SIG_IGN  (int (*)())1

Сигнали alarm

Системний виклик alarm (n) Викликає відправку вашому процесу сигналу SIGALRM черезn секунд Сигнал alarm може застосовуватися для того, щоб переконатися, що щось відбулося протягом належного проме моторошна часу якщо щось сталося, SIGALRM може бути вимкнений, якщо ж ні, то процес може повернути управління, отримавши сигнал alarm

Щоб пояснити ситуацію, розглянемо програму, яка називається time-out, вона запускає деяку іншу команду якщо ця команда не закінчилася до певного часу, вона буде аварійно перервана, коли alarm вимкнеться Наприклад, згадайте команду watchfor з глави 5 Замість того щоб запускати її на невизначений час, можна встановити часовий ліміт:

$ timeout  -3600  watchfor  dmg   &amp

Код в timeout ілюструє практично всі, про що говорилося в двох попередніх розділах Нащадок створено предок встановлює аварійний сигнал і чекає, поки нащадок закінчить свою роботу Якщо alarm приходить раніше, то нащадок знищується Робиться спроба повернути статус виходу нащадка

/ * Timeout: встановлює тимчасове обмеження для процесу * /

#include &ltstdioh&gt

#include &ltsignalh&gt

int pid / * Ідентифікатор дочірнього процесу * /

char  *progname main(argc,  argv)

int argc

char  *argv[]

{

int sec  = 10,  status,  onalarm()

progname = argv[0]

if  (argc &gt  1  &amp&amp  argv[1][0]  == –)  { sec  =  atoi(&ampargv[1][1])

argc–– argv++

}

if  (argc &lt 2)

error(&quotUsage: %s  [–10]  command&quot,  progname) if  ((pid=fork()) == 0)  {

execvp(argv[1],  &ampargv[1]) error(&quotcouldnt  start  %s&quot,  argv[1])

}

signal(SIGALRM,  onalarm) alarm(sec)

if (wait(&ampstatus) == –1  || (status &amp   0177)  =  0) error(&quot%s  killed&quot, argv[1])

exit((status &gt&gt 8)  &amp   0377)

}

onalarm () / * завершити дочірній процес у разі отримання alarm * /

{

kill(pid, SIGKILL)

}

Вправа 718Чи можете ви припустити, як реалізований sleep Підказка: pause (2) За яких умов (якщо такі умови існують) sleep і alarm можуть створювати перешкоди один для одного ~

Історія та бібліографія

У книзі не представлено докладного опису реалізації системи UNIX, зокрема через те, що існують майнові права на код Доповідь Кена Томпсона (Ken Thompson) «UNIX implementation» (Реалізація UNIX), виданий у «BSTJ» у липні 1978 року народження, описує основні ідеї Цю ж тему піднімають статті «The UNIX system – a retrospective» (Система UNIX в ретроспективі) в тому ж номері

«BSTJ» і «The evolution of the UNIX time-sharing system» (Еволюція UNIX – системи поділу часу), надрукована в матеріалах Symposium on Language Design and Programming Methodology в журналі видавництва Springer-Verlag «Lecture Notes in Computer Science»

№ 79 в 1979 році Обидва праці належать перу Денніса Рітчі (Dennis Ritchie)

Програма readslow була придумана Пітером Вейнбергер (Peter Weinberger) як простого засобу для демонстрації глядачам гри шахової програми Belle Кена Томсона і Джо Кондона (Joe Condon) під час шахового турніру Belle записувала стан гри в файл спостерігачі опитували файл за допомогою readslow, щоб не займати занадто багато дорогоцінних циклів (Нова версія обладнання для Belle здійснює невеликі розрахунки на своїй головній машині, тому більше такої проблеми не існує)

Том Дафф (Tom Duff) надихнув нас на написання spname Стаття Айво ра дерхемах (Ivor Durham), Девіда Лемба (David Lamb) і Джеймса Сакса (James Saxe) «Spelling correction in user interfaces» (Перевірка орфографії в призначених для користувача інтерфейсах), видана CACM в жовтні 1983 года, представляє дещо відрізняється від звичного проект реалізації виправлення орфографічних помилок у контексті поштового програми

Джерело: Керниган Б, Пайк Р, UNIX Програмне оточення – Пер з англ – СПб: Символ-Плюс, 2003 – 416 с, Мул

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*