Демоны АдаЧасть 2В прошлой статье мы рассмотрели общий для всех языков алгоритм создания демона. К сожалению выяснилось, что после вызова fork возникает небольшая проблема при использовании компилятора GNAT (хочу заметить что я не ручаюсь за то, что проблема только одна) — при попытке создать свои собственные обработчики системных прерываний программа наглухо зависает. Не знаю кто виноват, важней решить что можно сделать. Изготавливаем костыльПопробуем заменить fork полноценным запуском приложения (fork + execv). Вариант первый: сделать внешний костыль, который запускает демона и завершается. Вариант второй — процесс запускает самого себя и завершается, а к параметрам добавляет специальный секретный сигнализатор, например -daemonize. Остановимся на втором варианте. Вместе с параметром -daemonize нам придется также передать все остальные параметры, указанные при запуске (если они были). Конечно же это не обязательно — но в один прекрасный момент нам захочется через параметры указывать демону какой конфиг читать или что‐то еще, так что приготовимся к этому сразу. Единственное что может потом нервировать — так это постоянно помнить о том, что один из параметров „левый“. Сделаем функцию, которая соберет все переданные параметры воедино и последним добавит «-daemonize": function Get_Daemonize_Args return Argument_List is use Ada.Strings.Unbounded; Args : Unbounded_String := Null_Unbounded_String; begin if Argument_Count = 0 then return Argument_String_To_List ("--daemonize").all; else for i in 1 .. Argument_Count loop Args := Args & To_Unbounded_String (Argument (i) & " "); end loop; Args := Args & To_Unbounded_String ("--daemonize"); return Argument_String_To_List (To_String (Args)).all; end if; end Get_Daemonize_Args; Осталось научить демона полноценно запускать самого себя. Для этого воспользуемся методом Non_Blocking_Spawn из пакета Gnat.OS_Lib ID : Process_ID; if Argument_Count = 0 or else Argument (Argument_Count) /= "--daemonize" then ID := Non_Blocking_Spawn (Command_Name, Get_Daemonize_Args); if ID = Invalid_Pid then Log.Put ("Spawn return Invalid_Pid"); -- Пакет Log будет рассмотрен позже OS_Exit (-1); end if; Log.Put ("Pid is:" & Integer'Image (PID_To_Integer (ID))); OS_Exit (0); end if; То есть при запуске проверяем передавался ли параметр «-daemonize», если нет — запускаем самого себя уже с ним и завершаем выполнение. Дальше можно выполнять остальные требования по демонизации процесса. Вообще, во время работы демона желательно держать открытыми дескрипторы стандартных потоков ввода/вывода, поскольку они могут понадобиться различным функциям и процедурам стандартной библиотеки. Самое простое — после их закрытия (отстыковки от терминала) заново переоткрыть например на /dev/null : Stdin : File_Descriptor := Standin; Stdout : File_Descriptor := Standout; Stderr : File_Descriptor := Standerr; Close (Stdin); Close (Stdout); Close (Stderr); Stdin := Open_Read_Write ("/dev/null", Text); Stdout := Open_Read_Write ("/dev/null", Text); Stderr := Open_Read_Write ("/dev/null", Text); Желательно вообще не начинать каких‐либо еще операций с файлами до и во время этого блока, только после (для параноидальности можно еще проверить что Stdin действительно стал 0, Stdout = 1, а Stderr = 2). Обработка прерыванийС помощью обработки сигналов организуются многие вещи, например корректный останов/перезапуск, «передёргивание» лог‐файлов и прочее. Создадим простой шаблон обработчика прерываний, для этого его желательно (если вообще не необходимо) расположить в отдельном пакете Спецификация: -- File: sig.ads with Ada.Interrupts; with Ada.Interrupts.Names; package SIG is SIGTERM : Ada.Interrupts.Interrupt_ID := Ada.Interrupts.Names.SIGTERM; protected type P_SIG (Int_ID : Ada.Interrupts.Interrupt_ID) is procedure Handler; pragma Attach_Handler (Handler, Int_ID); end P_SIG; type P_SIG_Access is access P_SIG; end SIG; Реализация: -- File: sig.adb with Log; package body SIG is protected body P_SIG is procedure Handler is use Ada.Interrupts; begin if Int_ID = SIGTERM then Log.Put ("Signal:" & Int_ID'Img & ". Fuck Yea!"); else Log.Put ("Signal:" & Int_ID'Img & "."); end if; end Handler; end P_SIG; end SIG; Здесь мы использовали вложенный стиль установки обработчика стандартной модели прерываний Ада. Теперь повесим на него обработку 15‐го сигнала (SIGTERM) и, например, 10‐го (USR1, хотя номер может отличаться в разных системах) SIGTERM_Access : SIG.P_SIG_Access; SIGUSR1_Access : SIG.P_SIG_Access; SIGTERM_Access := new SIG.P_SIG (SIG.SIGTERM); SIGUSR1_Access := new SIG.P_SIG (10); Проверить можно командой kill $ kill pid По умолчанию посылается SIGTERM $ kill -USR1 pid Логи, syslogВ качестве примера сначала сделаем простой метод для сохранения логов (Log.Put упоминавшийся ранее). В конечном итоге он будет реализован в виде защищенной процедуры, пока приведем реализацию: FN : constant String := "/tmp/adaemon.log"; procedure Put (Msg : String) is use Ada.Text_IO; FD : File_Type; Time : Ada.Calendar.Time := Ada.Calendar.Clock; begin if not Ada.Directories.Exists (FN) then Create (FD, Out_File, FN); else Open (FD, Append_File, FN); end if; Put_Line (FD, Ada.Calendar.Formatting.Image (Time) & " " & Msg); Close (FD); end Put; Тут все тривиально. Теперь посмотрим как можно отправлять сообщения в системный журнал. Для этого нужно воспользоваться функциями openlog, syslog и closelog, описанными в syslog.h. К сожалению, функция syslog имеет переменное число аргументов и ее нельзя корректно импортировать в Аду (ввиду особенностей ABI архитектуры amd64). Изрядно помучавшись пришлось сделать Си‐шную обертку и импортировать уже ее. // File: syslog_wrapper.c #include <syslog.h> syslog_wrapper (int priority, const char* msg) { openlog ("ada_test", LOG_NDELAY|LOG_PID, LOG_DAEMON); syslog(priority, "%s", msg); closelog(); } procedure Syslog (Msg : String) is use Interfaces.C; use Interfaces.C.Strings; procedure Syslog_Wrapper (Priority : int; Msg : chars_ptr); pragma Import (C, Syslog_Wrapper, "syslog_wrapper"); begin Syslog_Wrapper (6, New_String (Msg)); -- 6 - LOG_INFO end Syslog; Echo сервер В качестве заключительного примера сделаем простой сетевой Echo сервер -- File: main.adb with LOG; with Gnat.Sockets; with Ada.Exceptions; with Ada.IO_Exceptions; use Gnat.Sockets; procedure Main is Address : Sock_Addr_Type; Server : Socket_Type; Socket : Socket_Type; Channel : Stream_Access; begin Initialize; Create_Socket (Server); Set_Socket_Option (Server, Socket_Level, (Reuse_Address, True)); Bind_Socket (Server, (Family_Inet, Inet_Addr ("127.0.0.1"), 2300)); Listen_Socket (Server); loop Accept_Socket (Server, Socket, Address); Log.Syslog ("Client connected from " & Image (Address)); Channel := Stream (Socket); declare begin loop Character'Output (Channel, Character'Input (Channel)); end loop; exception when Ada.IO_Exceptions.End_Error => null; end; Close_Socket (Socket); end loop; exception when The_Event: others => Log.Put (Ada.Exceptions.Exception_Information (The_Event)); end Main; Тут без комментариев, после выполнения всех шагов по демонизации просто вызываем Main. Архив с собранными воедино исходниками можно скачать здесь Сборка: $ gcc -c syslog_wrapper.c $ gnatmake daemon -largs syslog_wrapper.o После удачной сборки и запуска проверяем работу: $ telnet 127.0.0.1 2300 Пробуем посылать сигналы демону через kill и проверяем лог файл /tmp/adaemon.log, а также пишется ли что‐нибудь в syslog от демона при подключении (скорее всего это будет /var/log/daemon.log). PS. Не забудьте вырубить демона через kill -9 :-D (c) Gavrikov Valeriy ([email protected]), April 2012 Автор: Валерий Гавриков |