Як демонізувати процес

Створення демона не є особливо складним, але воно включає в себе деякі частини системи, з якими розробники часто не мають справу явно, і можуть не розуміти їх добре. Це може призвести до карго-культового програмування, а іноді й до помилок.

Кулінарна книга

Ось проста процедура Perl, яка демонізує процес

use POSIX;
sub daemonize
{
fork and exit;
POSIX::setsid();
fork and exit;
umask 0;
chdir '/';
close STDIN;
close STDOUT;
close STDERR;
}

Якщо демон має код, який покладається на STDIO, ви можете повторно відкрити дескриптори системних файлів у /dev/null

sub daemonize

sub daemonize
{
    fork and exit;
    POSIX::setsid();
    fork and exit;
    umask 0;
    chdir '/';
    open STDIN , '<', '/dev/null';     open STDOUT, '>', '/dev/null';
    open STDERR, '>', '/dev/null';

}

Якщо ви хочете створити іншу програму, додайте виклик exec. Залиште дескриптори системних файлів відкритими для виконуваної програми.

sub daemonize
{
    fork and exit;
    POSIX::setsid();
    fork and exit;
    umask 0;
    chdir '/';
    exec 'my-daemon';
}

Якщо ви хочете, щоб батьківський елемент продовжив виконання, виконайте повернення замість виходу

sub daemonize
{
    fork and return;
    POSIX::setsid();
    fork and exit;
    umask 0;
    chdir '/';
    exec 'my-daemon';

Для робочого коду додайте перевірку помилок


use POSIX;
sub Fork
{
    my $pid = fork;
    defined $pid or die "Can't fork: $!\n";
    $pid
}

sub daemonize

{
    Fork and return;
    POSIX::setsid();
    Fork and exit;
    umask 0;
    chdir '/' or die "Can't chdir to /: $!\n";
    exec 'my-daemon'
    die "Can't exec my-daemon: $!\n";

}

Аналіз

Щоб створити демон, вам потрібно зробити шість речей.

вилка (перший раз)

  1. Перше розгалуження дозволяє нащадковій системі запускати демон, поки батьківська програма продовжує виконання або виходить. Крім того, це гарантує, що дочірній процес не є лідером групи процесів, тому наступний виклик setsid() буде успішним.
  2. setsid

Таким чином ми втрачаємо контрольний термінал. setsid робить наш процес лідером нового сеансу, і коли створюється новий сеанс, лідер сеансу не має керуючого терміналу.

  1. вилка (вдруге)

Лідер нового сеансу не має керуючого терміналу, але він може отримати його, наприклад, відкривши /dev/tty або подібне. Щоб цього не сталося, ми знову розгалужуємося.

Після другого розгалуження дочірній елемент не є лідером сеансу (батьківський є лідером сеансу), і лише лідер сеансу може отримати керуючий термінал. Звичайно, дитина може стати лідером сеансу, виконавши setsid знову, але, мабуть, кожен, хто це робить, знає, що робить.

  1. umask 0

umask успадковується через fork і exec, і ми не хочемо, щоб наш демон був обмежений будь-якою umask, яку він успадкував від свого батька.

  1. chdir ‘/’

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

  1. закрити

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

У Perl несистемні файлові дескриптори (вище $^F) автоматично позначаються як close-on-exec.

Деякі модулі Perl дублюють STDERR. Ви можете закрити STDERR, але дубльований дескриптор залишається відкритим на терміналі. Один із способів уникнути цього — закрити STDERR у блоці BEGIN перед завантаженням модулів

BEGIN { close STDERR; }

Якщо це не зручно, ви можете відшукати дубльовані дескриптори та закрити їх за допомогою такого коду

my $fd2    = "/proc/$$/fd/2";
my $stderr = readlink $fd2 or die "Can't readlink $fd2: $!\n";
for my $link ()
{
    my($fd) = $link =~ /(\d+)$/;
    $fd > $^F or next;
    readlink($link) eq $stderr and POSIX::close($fd);
}

Контекст

Більшість плутанини пов’язана з викликом setsid(). Довідкові сторінки setsid(2) і setpgid(2) містять відповідні факти; однак, як це типово для документації Unix, вони надають мало контексту.

Ось трохи контексту.

Процеси та термінали

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

  • введення (клавіатура)
  • вихід (екран)
  • сигнали (Ctrl-C, Ctrl-Z, перерва тощо)

Термінал, налаштований на надання цих речей, називається керуючим терміналом для процесу. Процес може мати або не мати керуючого терміналу. Як правило, ви хочете, щоб процеси, які ви запускаєте в сеансі оболонки, наприклад ls або cat, мали керуючий термінал. Ось як ви ними керуєте. І навпаки, ви хочете, щоб демони не мали керуючого терміналу.

  • демон не повинен читати введені дані з клавіатури (хто на ньому друкуватиме?)
  • демон не повинен друкувати вихідні дані на екран (хто збирається це читати?)

і, мабуть, найважливіше

  • демон не повинен отримувати сигнали від терміналу

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

Особливою проблемою є SIGHUP. HUP – це скорочення від Hang UP; термінологія сягає часів, коли люди підключали фізичні термінали до комп’ютерів за допомогою модемів і телефонних ліній.

Багато програмістів розпізнають SIGHUP як сигнал, який ви надсилаєте демону, щоб наказати йому перечитати файли конфігурації. Однак початкове використання SIGHUP полягало в тому, щоб повідомити процесу, що телефонне з’єднання з його терміналом було розірвано: що він буквально більше не мав керуючого терміналу. Стандартною дією для процесу, який отримує SIGHUP, є завершення (див. сигнал (7)).

Сучасним еквівалентом фізичного терміналу є псевдотермінал типу XTerm або PuTTY. Коли псевдотермінал завершує роботу (наприклад, через те, що користувач закриває вікно на своєму екрані), ОС надсилає SIGHUP процесам, які контролюються цим терміналом. (Детальніше дивіться у setpgid(2).) Якщо ваш демон все ще має керуючий термінал, він може завершити роботу, коли це станеться. Отже, ви хочете, щоб ваш демон не мав керуючого терміналу.

Правильно запущений демон без керуючого терміналу ніколи не отримає SIGHUP від ОС. Таким чином, зручно змінити призначення SIGHUP, щоб наказати демону читати файли конфігурації.

Сесії та групи процесів

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

% cat > foo

тоді ви хочете, щоб STDIN, Ctrl-D і Ctrl-C були спрямовані на кота, а не на вашу оболонку.

Якщо ти зробиш

% cat foo | sort | more

тоді ви хочете мати можливість використовувати STDIN, щоб контролювати більше, і ви хочете, щоб Ctl-C вимкнув увесь конвеєр, що означає, що він повинен бути доставлений до всіх трьох процесів.

Якщо ти зробиш

% sort foo > bar &
% more baz

тоді ви хочете, щоб сортування тихо виконувалося у фоновому режимі, поки ви використовуєте термінал для перегляду баз.

Щоб керувати всім цим, Unix надає деякі додаткові структури для процесів

  • процеси організовані в групи процесів
  • групи процесів організовані в сесії

Це сувора ієрархія стримування. Це виглядає як

+-session 1 ----------------+
|                           |
|  +-process group 1-----+  |
|  |                     |  |
|  | process 1           |  |
|  | process 2           |  |
|  |                     |  |
|  +---------------------+  |
|                           |
|  +-process group 3-----+  |
|  |                     |  |
|  | process 3           |  |
|  | process 4           |  |
|  | process 5           |  |
|  |                     |  |
|  +---------------------+  |
|                           |
+---------------------------+

Загалом, сеанс відповідає термінальному сеансу: оболонка та всі процеси, які оболонка виконує від імені користувача. Коли ви запускаєте новий термінал, ОС створює для вас новий сеанс, а оболонка стає лідером сеансу.

Зазвичай оболонка створює нову групу процесів для кожного конвеєра, який вона виконує (навіть якщо в конвеєрі є лише один процес).

Будь-який процес у сеансі може писати на термінал. Ось чому ви іноді бачите вихід фонових процесів посеред чогось іншого.

Одна група процесів призначена для переднього плану; всі інші групи процесів працюють у фоновому режимі. Процеси в групі переднього плану можуть читати з терміналу; процеси у фоновому режимі будуть заблоковані, якщо вони спробують прочитати з STDIN. Якщо сигнал генерується терміналом (наприклад, Ctrl-C), тоді цей сигнал доставляється кожному процесу в групі процесів переднього плану.

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

Процес, який хоче вийти з системи, щоб не бути в тому самому сеансі, що й оболонка, викликає setsid(). Потім він стає лідером нового сеансу, і цей сеанс не має керуючого терміналу.