Демони, комп'ютерна документація від а до я

Демонами в світі Unix традиційно називаються процеси, які не взаємодіють з користувачем безпосередньо. У процесу-демона немає керуючого терміналу і немає, відповідно, призначеного для користувача інтерфейсу. Для управління демонами доводиться використовувати інші програми. Сама назва «демони» виникло завдяки тому, що багато процесів цього типу більшу частину часу проводять в очікуванні якоїсь події. Коли ця подія настає, демон активізується (вистрибує, як чортик з табакерки), виконує свою роботу і знову засинає в очікуванні події. Слід зазначити, що багато демони, такі як, наприклад, Web-сервер або сервер баз даних, можуть відбирати на себе практично всі процесорний час і інші ресурси системи. Такі демони набагато більше працюють, ніж сплять.

Зараз ми пропустимо блок операторів if (argc> 1) (ми повернемося до нього пізніше) і розглянемо основні етапи роботи демона. Функція BecomeDaemonProcess () перетворює звичайний консольний процес Linux в процес-демон. Функція ConfigureSignalHandlers () налаштовує обробники сигналів процесу-демона, а функція BindPassiveSocket () відкриває певні порт TCP / IP для прослуховування вхідних запитів. Далі слід цикл, в якому сервер обробляє запити. Багато мережних сервери, отримавши запит, створюють дочірній процес для його обробки. Таким чином досягається можливість паралельної обробки запитів. Деякі сервери використовують для паралельної обробки запитів потоки. Що стосується нашого сервера, то з міркувань простоти він обробляє запити в послідовному (блокирующем) режимі. Адже ми не очікуємо, що наш демонстраційний сервер буде отримувати багато запитів, чи не так?

Нормальний вихід з циклу обробки запитів відбувається при отриманні процесом сигналу SIGUSER1. Після виходу з циклу процес викликає функцію TidyUp () і завершує роботу. Ми, безумовно, можемо завершити процес-демон, пославши йому сигнал SIGKILL (SIGTERM і деякі інші), але для користувача сигнал SIGUSER1 гарантує ввічливе завершення нашого демог. «Вежливое завершення» означає, що сервер відповість на поточний запит перед тим, як завершитися і видалить свій pid- файл.

Ми робимо кореневу директорію поточної Директорією процесу-демона. Будучи запущений, наш демон може працювати аж до перезавантаження системи, тому його поточна діретокрія повинна належати файлової системи, яка не може бути демонтувати. Далі слід виклик lockFD = open (lockFileName, O_RDWR | O_CREAT | O_EXCL, 0644);

Кожен процес-демон створює так званий pid-файл (або файл блокування). Цей файл зазвичай міститься в директорії / var / run і має ім'я daemon.pid, де "daemon" відповідає імені демона. Файл блокування містить значення PID процесу демона. Цей файл важливий з двох причин. По-перше, його наявність дозволяє встановити, що в системі вже запущений один екземпляр демона. Більшість демонів, включаючи наш, повинні виполняяться не більше ніж в одному екземплярі (це логічно, якщо врахувати, що демони часто звертаються до неподілюваний ресурсів, таким, як мережеві порти). Завершуючись, процес-демон видаляє pid-файл, вказуючи тим самим, що можна запустити другий примірник процесу. Однак, робота демона не завжди завершується нормально, і тоді на диску залишається pid-файл неіснуючого процесу. Це, здавалося б, може стати непереборною перешкодою для повторного запуску демона, але насправді, демони успішно справляються з такими ситуаціями. У процесі запуску демон перевіряє наявність на диску pid-файлу з відповідним ім'ям. Якщо такий файл існує, демон зчитує з нього значення PID і за допомогою функції kill (2) перевіряє, чи існує в системі процес із зазначеним PID. Якщо процес існує, значить, користувач намагається запустити демон повторно. У цьому випадку програма виводить відповідне повідомлення і завершується. Якщо процесу з зазначеним PID в системі немає, значить pid-файл належав аварійного завершеному демона. У цій ситуації програма зазвичай радить користувачеві видалити pid-файл (відповідальність в таких справах завжди краще перекласти на користувача) і спробувати запустити її ще раз. Може, звичайно, статися і так, що після аварійного завершення демона на диску залишиться його pid-файл, а потім якийсь інший процес отримає той же самий PID, що був у демона. У цій ситуації для знову запускається демона все буде виглядати так, як ніби його копія вже працює в системі, і запустити демон повторно ви не зможете. На щастя, описана ситуація вкрай малоймовірна.

Друга причина, по якій файл блокування вважається корисним, полягає в тому, що за допомогою цього файлу ми можемо швидко з'ясувати PID демона, не вдаючись до команди ps.

Далі наш демон викликає функцію fork (3), яка створює копію його процесу. Батьківський процес при цьому завершується:

Робиться це для того щоб процес-демон відключився від керуючого терміналу. З кожним терміналом Unix пов'язаний набір груп процесів, іменований сесією. У кожен момент часу тільки одна з груп процесів, що входять в сесію, має доступ до терміналу (тобто, може виконувати введення / виведення за допомогою терміналу). Ця група іменується foreground (пріоритетною). У кожній сесії є процес-родоначальник, який називається лідером сесії. Якщо процес-демон запущений з консолі, він, природно, стає частиною пріоритетною групи процесів, що входять в сесію відповідного терміналу.

Для того щоб відключитися від терміналу, демон повинен почати нову сесію, не пов'язану з яким-небудь терміналом. Для того щоб демон міг почати нову сесію, він сам не повинен бути лідером якої-небудь іншої сесії. Виклик fork () створює дочірній процес, який свідомо не є лідером сесії. Далі дочірній процес, отриманий за допомогою fork (), починає нову сесію з допомогою виклику функції setsid (2). При цьому процес стає лідером (і єдиним учасником) нової сесії.

Отже, на даному етапі ми маємо процес, не пов'язаний з будь-яким терміналом. Далі в деяких посібниках рекомендується викликати fork () ще раз, щоб новий процес перестав бути лідером нової сесії (в System V лідер сесії може автоматично отримати керуючий термінал при деяких умовах). У Linux в повторному виклику fork () немає необхідності і ми його робити не будемо.

Варто відзначити, що тепер наш демон отримав новий PID, який ми знову повинні записати в pid-файл демона. Ми записуємо значення PID в файл в строковому вигляді (а не як змінну типу pid_t). Робиться це для зручності користувача, щоб значення PID з pid-файлу можна було прочитати за допомогою cat. наприклад:

Нашому демона вдалося розірвати зв'язок з терміналом, з якого він був запущений, але він все ще може бути пов'язаний з іншими процесами і файловими системами через файлові дескриптори, успадковані від батьківських процесів. Для того щоб розірвати і цей зв'язок, ми закриваємо всі файлові дескриптори, відкриті в нашому процесі:

Функція sysconf () з параметром _SC_OPEN_MAX повертає максимально можливу кількість дескрипторів, які може відкрити наша програма. Ми викликаємо функцію close () для кожного дескриптора (незалежно від того, відкритий він чи ні), за винятком дескриптора pid- файлу, який повинен залишатися відкритим.

Під час роботи демона дескриптори стандартних потоків введення, виведення і помилок також повинні бути відкриті, оскільки вони необхідні багатьох функцій стандартної бібліотеки. У той же час, ці дескпрітори не повинні вказувати на будь-які реальні потоки введення / виводу. Для того, щоб вирішити цю задачу, ми закриваємо перші три дескриптора, а потім знову відкриваємо їх, вказуючи в якості імені файлу / dev / null:

Тепер ми можемо бути впевнені, що демон не отримає доступу до якого-небудь терміналу. Проте, у демона має бути можливість виводити кудись повідомлення про свою роботу. Традиційно для цього використовуються файли журналів (log-файли). Файли журналів для демона подібні чорних скриньок літаків. Якщо в роботі демона стався якийсь збій, користувач може проаналізувати файл журналу і (при певному везінні) встановити причину збою. Ніщо не заважає нашому демона відкрити свій власний файл журналу, але це не дуже зручно. Більшість демонів користуються послугами утиліти syslog, яка веде журнали безлічі системних подій. Ми відкриваємо доступ до журналу syslog за допомогою функції openlog (3):

Перший параметр функції openlog () - префікс, який буде додаватися до кожного запису в системному журналі. Далі йдуть різні опції syslog. Функція setlogmask (3) дозволяє встановити рівень пріоритету повідомлень, які записуються в журнал подій. При виконанні функції BecomeDaemonProcess () ми передаємо в параметрі logLevel значення LOG_DEBUG. У поєднанні з макросом LOG_UPTO це означає, що в журнал будуть записуватися всі повідомлення з пріоритетом, починаючи з найвищого і закінчуючи LOG_DEBUG.

Останнє, що нам потрібно зробити для «демонізації» процесу - викликати функцію setpgrp ();

Цей виклик створює нову групу процесів, ідентифікатором якої є ідентифікатор поточного процесу. На цьому робота функції BecomeDaemonProcess () завершується, так як тепер наш процес став справжнім демоном.

Функція ConfigureSignalHandlers () налаштовує обробники сигналів. Сигнали, які отримає наш демон, можна розділити на три групи: ігноровані, «фатальні» і оброблювані. Викликаючи функцію signal (SIGUSR2, SIG_IGN);

Функція-обробник FatalSigHandler () записує в журнал подій інформацію про отриманий сигнал, і потім завершує процес, викликавши перед цим функції closelog () і TidyUp (), які вивільняють все зайняті процесом ресурси:

Оброблювач TermHandler () викликає функцію TidyUp () і завершує процес. Оброблювач Usr1Handler () робить в системному журналі запис про ввічливому завершення процесу і привласнює змінної gGracefulShutdown значення 1 (що, як ви пам'ятаєте, призводить до виходу з циклу обробки запитів, коли цикл буде готовий до цього). Оброблювач сигналу HupHandler () також робить запис в системному журналі, після чого привласнює значення 1 змінним gGracefulShutdown і gCaughtHupSignal. У реальному житті отримання сигналу SIGHUP призводить до перезапуску демона, який супроводжується повторним прочитанням файлу конфігурації (який зазвичай читається демоном саме під час запуску) і перевстановлення значень записаних в ньому параметрів. Саме необхідність прочитати повторно файл конфігурації є найбільш частою причиною перезапуску демонів. У нашого демона файлу конфігурації немає, так що в процесі перезапуску робити йому особливо нічого.

Зверніть увагу на тип змінних gGracefulShutdown і gCaughtHupSignal. З типом sig_atomic_t ми раніше не зустрічалися. Застосування цього типу гарантує, що читання і запис даних в змінні gGracefulShutdown і gCaughtHupSignal буде виконуватися атомарному, однією інструкцією процесора, яка не може бути перервана. Атомарність при роботі зі змінними gGracefulShutdown і gCaughtHupSignal важлива потоуму, що до них можуть одночасно отримати доступ і обробники сигналів, і головна функція програми. З цієї ж причини ми помічаємо зазначені змінні ключовим словом volatile.

Функція BindPassiveSocket () відкриває для прослуховування порт сервера (в нашому випадку це порт 30333) на всіх доступних мережеві інтерфейси і повертає відповідний сокет:

Тим, хто читав статтю цієї серії, присвячену сокета, має бути зрозуміло, що тут відбувається. Відзначимо тільки одну цікаву деталь. Якщо попередній, вже закритий, сокет, пов'язаний з даним портом, знаходиться в стані TIME_WAIT, між закриттям старого і відкриттям нового сокета може статися затримка, рівна двом періодам життя сегмента (затримка може становити до двох хвилин). Для того, щоб при повторному запуску демона нам не довелося чекати, ми використовуємо функцію setsockopt () з параметром SO_REUSEADDR.

Функція AcceptConnections () обробляє запити послідовно, використовуючи блокуючий виклик accept ():

Це не кращий спосіб поведінки демона, але якщо ми почнемо описувати паралельну обробку запитів, редакція не витримає. Мінлива proceed, спільно зі змінною gGracefulShutdown, вказує, чи повинна програма продовжувати обробляти запити. Якщо черговий виклик connect () або HandleConnection () повернув повідомлення про помилку, цієї змінної присвоюється 0 і обробка запитів припиняється. Новий сокет, отриманий в результаті виклики accept (), передається функції HandleConnection ().

Функція HandleConnection () зчитує передану клієнтом рядок і тут же повертає її клієнту. Потім функція AcceptConnections () закриває з'єднання, відкрите в результаті виклику accept (). Функції ReadLine () і WriteToSocket () тривіальні, і розглядати їх ми не будемо. Якщо десь в ланцюжку викликів AcceptConnections (), HandleConnection (), ReadLine () і WriteToSocket () виникла помилка, інформація про помилку буде передаватися вгору по ланцюжку до тих пір, поки не досягне функції main (). У функції main () ця інформація призведе до негайного завершення роботи демона з відповідним записом в журнал системних повідомлень.

Розглянемо, нарешті, функцію TidyUp (), до якої звертаються багато функцій сервера перед тим, як завершити його роботу.

Завдання функції TidyUp () - «прибрати сміття» за процесом-демоном. В принципі, без цієї функції можна обійтися, так як після завершення процесу система сама закриє всі його дескриптори, але правила хорошого тону вимагають явного вивільнення всіх ресурсів, виділених явно.

Якщо ви скомпілюєте програму-демон з допомогою команди

Те зможете запустити демон командою

Оскільки демон потребує доступу до директорії / var / run, запускати його потрібно в режимі root. Відразу після запуску програми ви знову побачите запрошення командного рядка, що для демонів абсолютно нормально. Якби сервер aahzd виконував що-небудь корисне, команду його запуску можна було б прописати в одному зі сценаріїв запуску системи в директорії /etc/init.d, але ми цього робити не будемо. Після того як сервер запущений, ви можете дати команду

В результаті буде встановлено з'єднання з сервером. Надрукуйте якусь послідовність символів в консолі telnet і натисніть «Вввод». Як відповідь сервер поверне надруковану рядок і закриє з'єднання.

Повернемося тепер до початкових рядках функції main (). Хоча ми можемо отримати PID демона з його pid-файлу і управляти демоном за допомогою команди kill, такий варіант не можна назвати дуже зручним. Часто для управління демоном використовується сам виконуваний файл демона, який запускається зі спеціальними аргументами командного рядка. Наш демон розуміє дві команди: stop (завершення роботи демона) і restart (перезапуск). Подивимося, як поведеться демон, запущений з аргументами командного рядка. У цьому випадку на початку програми демон намагається вважати значення PID зі свого pid-файлу. Якщо відкрити pid-файл не вдається, значить, швидше за все, чорт не запущений, і керуючому режиму просто нічого робити. Якщо значення PID отримано, процес, керуючий демоном, посилає демона відповідний сигнал за допомогою функції kill ().

Демони не розраховані на те, щоб повчати будь-яку інформацію від користувача. Власну інформацію вони передають іншим програмам або записують в журнали системних подій. У наступній статті ми зосередимося на програмах, які ведуть себе зовсім інакше і розглянемо консольне введення-виведення.

Схожі статті