Главная > Linux > Создание демона (службы) в Linux

Создание демона (службы) в Linux

Чего я никак не мог предположить, так это того, что на моей работе мне представится возможность или скорее появится необходимости писать что-нибудь под linux. Однако такое событие произошло. Несмотря на то, что по долгу службы я являюсь PHP-разработчиком (веб-разработчиком) мне «предложили» написать утилиту под Ubuntu для начинающегося проекта. И не просто утилиту а демона — службу под linux. Еще пару лет назад подобного рода задача могла бы испугать меня. Но только не теперь. Тем более что для разработки под Linux совсем необязательно иметь специфическое программное обеспечение. Компилятор C++ уже имеется. Однако я давно собирался попробовать кросс платформенную среду для разработки ПО Lazarus. И я решил создавать демона на FreePascal.

Итак, здесь я попытаюсь показать пример создания простейшего демона в Lazarus.

Структура основной программы выглядит также, как и обыкновенного консольного приложения:

program mydaemon;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
SysUtils
Begin
End.

Основная идея при создании демона заключается в следующем: создается еще один экземпляр приложения в памяти а текущая программа завершается. Таким образом и стартует демон. Для реализации этого подхода используется функция fpFork. Она создает копию текущего процесса и возвращает:

  • PID, если процесс успешно создан;
  • -1, если возникла ошибка при создании процесса;
  • 0, если это уже копия процесса (потомок).

Таким образом старт демона осуществляется при помощи несложных операций:

pid := fpFork;
case pid of
0 : begin { we are in the child }
Close(input); { close standard in }
Assign(input,'/dev/null');
ReWrite(input);
Close(output); { close standard out }
Assign(output,'/dev/null');
ReWrite(output);
Close(stderr); { close standard error }
pid:=fpGetPid;
end;
-1 :begin
WriteLn('forking error, so halt 1');
halt(1);
end;
else begin
Halt;          { successful fork, so parent dies }
end;

При запуске программы мы делаем копию нашего процесса, и если это прошло успешно, завершаем работы (попадаем в ветку case else). Дочерний же процесс начинает свою работу, попадая в ветку case 0 и следует далее по коду программы. Теперь демон переходит в режим ожидания и ждет сигнала, после которого он активизируется и выполнит определенные действия. В простейшем случае это можно организовать в виде цикла:

while not bTerm do
begin
......
end;

Этот цикл будет выполняться до тех пор пока не придет сигнал об уничтожении. Теперь необходимо научить демон принимать внешние сигналы от ОС. Для этого создадим функцию, которая будет обрабатывать сигналы:

procedure DoSig(sig : longint);cdecl;
begin
case sig of
SIGTERM : bTerm := true;
end;
end;

И теперь перенаправим обработку сигналов на эту функцию:

{$hints off}
FpsigEmptySet(zerosigs);
{$hints on}
{ set global daemon booleans }
bTerm := false;

{ block all signals except -TERM }
sSet := $ffffbffe;
ps1 := @sSet;
fpsigprocmask(sig_block,ps1,nil);

{ setup the signal handlers }
new(aOld);
new(aTerm);
aTerm^.sa_handler{.sh} := SigactionHandler(@DoSig);
aTerm^.sa_mask := zerosigs;
aTerm^.sa_flags := 0;
fpSigAction(SIGTERM,aTerm,aOld)

Теперь, если наш демон получит сигнал об уничтожении sigTerm, он будет завершен. Остается придумать механизм запуска и остановки демона. Сейчас я реализовал это довольно простым способом. При запуске демона проверяется какой параметр ему передан. Если start, то форким, создаем во временной директории файл daemonname.lock и пишем туда pid демона. Если же в качестве параметра передан stop открываем файл daemonname.lock, читаем pid, убиваем процесс с таким pid, удалем lock файл. Однако, вероятно, это не лучшее решение. Поэтому предстоит еще провести исследование в этом направлении.

Полный код демона:

program mydaemon;

{$mode objfpc}{$H+}

uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
SysUtils,BaseUnix, logger, mydb, VideoRecorder, socketserver;

Var
{ vars for daemonizing }
bTerm : boolean;
aOld,
aTerm: pSigActionRec;
ps1  : psigset;
sSet : cardinal;
pid  : pid_t;
zerosigs : sigset_t;
status: integer;
secs : longint;
FileEngine:TLogger;
err: LongInt;
counter:integer;
db:TMyDB;
Socket:TSocketServer;
CurrentQueue:TQueueInfo;
{ handle SIGTERM }
procedure DoSig(sig : longint);cdecl;
begin
case sig of
SIGTERM : bTerm := true;
end;
end;

procedure RunDaemon();
var
Player:TVideoRecorder;
begin
db:=TMyDB.Create('localhost', 'root', 'college', 'daemon');
writeln('Connecting...');
if not db.ConnectToDatabase then
begin
writeln('Error connection to database:');
writeln(db.getError());
FileEngine.WriteLog(db.getError);
halt;
exit;
end;
{$hints off}
FpsigEmptySet(zerosigs);
{$hints on}
{ set global daemon booleans }
bTerm := false;

{ block all signals except -TERM }
sSet := $ffffbffe;
ps1 := @sSet;
fpsigprocmask(sig_block,ps1,nil);

{ setup the signal handlers }
new(aOld);
new(aTerm);
aTerm^.sa_handler{.sh} := SigactionHandler(@DoSig);

aTerm^.sa_mask := zerosigs;
aTerm^.sa_flags := 0;
fpSigAction(SIGTERM,aTerm,aOld);
{ daemonize }
pid := fpFork;
case pid of
0 : begin { we are in the child }
Close(input); { close standard in }
Assign(input,'/dev/null');
ReWrite(input);
Close(output); { close standard out }
//Assign(output,'dpsoutput.log');
//ReWrite(output);
Assign(output,'/dev/null');
ReWrite(output);
Close(stderr); { close standard error }
pid:=fpGetPid;
FileEngine.Lock(pid);
writeln('Daemon Started');
end;
-1 :begin
WriteLn('forking error, so halt 1');
halt(1);
end;
else begin
Halt;          { successful fork, so parent dies }
end;
end;
FileEngine.WriteLog('Daemon start');
{ begin processing loop }
counter:=0;
Player:=TVideoRecorder.Create;
repeat
//Some actions
until bTerm;
Player.Free;
FileEngine.UnLock;
FileEngine.WriteLog('Daemon terminated');
db.Free;
end;

Begin
{Init File Engine}
FileEngine:=TLogger.Create('test');
{**Check for superuser rights**}
{ if fpGetUID<>0 then
begin
Writeln('Error: Need superuser rights');
halt;
end;}
{****}
if ParamStr(1)='start' then
begin
if FileEngine.isLockFileExists then
begin
WriteLn('Process already running');
halt;
end;
Writeln('Startin Daemon...');
RunDaemon;
end
else if ParamStr(1)='stop' then
begin
writeln('Stoping daemon');
if FileEngine.isLockFileExists then
begin
pid:=FileEngine.getPID;
if fpkill(pid, SIGTERM) < 0 then
begin
err := fpGetErrno;
case err of
ESysEsrch:
begin
Writeln(stderr,'Porcces with PID='+IntToStr(pid)+' not exists');
FileEngine.UnLock;
end;
ESysEperm: Writeln(stderr,'Permissions to kill process denied');
end;
end
else
begin
Writeln(stderr,'Daemon stopped');
end;
end
else
Writeln('Daemon is not running');
end
else writeln('Usage: ./mydaemon start|stop|restart');
End.
Реклама
  1. Май 28, 2010 в 3:15 пп

    Really awesome writing! Really!

  2. df
    Сентябрь 24, 2011 в 2:41 пп

    спасибо, очень помогло!

  1. No trackbacks yet.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход /  Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход /  Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход /  Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход /  Изменить )

w

Connecting to %s

%d такие блоггеры, как: