Divertindo-se com Daemons

Daniel de Oliveira Mantovani
Publicado em 01/03/2011

Divertindo-se com Daemons

Origem

Nesse artigo você irá aprender a criar daemons. Acredito que ficará contente em saber que essa é uma tarefa bem simples, na verdade.

O termo "daemon" foi cunhado pelos programadores do "Projeto MAC", do MIT, baseando-se no Demônio de Maxwell, um ser imaginário que realiza tarefas sem ser visto. O termo é derivado da palavra grega daimon que significa "ser sobrenatural", "espírito". Mais tarde, algumas pessoas tentaram definir o termo como um acrônimo para "Disk And Execution MONitor".

Um passo Para Esquerda

Para o conteúdo deste artigo, presumimos que o leitor esteja usando um Sistema Operacional baseado em UNIX que siga o POSIX, como o Linux. Os exemplos desse artigo não funcionarão no Windows, talvez se você conheça alguma mágica que implemente fork(), os exemplos funcionem. http://search.cpan.org/~rjbs/perl-5.12.3/pod/perlfork.pod

Martelo de Ouro

"if all you have is a hammer, everything looks like a nail".

Uma série de programas usam daemon, como o sshd, ftpd, apache e etc. Muitas vezes apenas queremos executar uma determina rotina em um período de tempo, não se esqueça do cron, uma solução nativa desses sistemas para o agendamento de tarefas.

Em situações onde é esperado um evento que ocorra a qualquer hora, onde a tarefa é frequente, um daemon é necessário. Um daemon é eficiente porque reside na memória e não exige todo o "run script", evitando os ciclos da sua CPU e I/O que são usados quando você chama o seu programa.

Os Mandamentos

Existem algumas regras para a criação de um daemon, a seguir veremos essas regras separadas e explicadas.

O 1º Mandamento

Desassociar o controle do terminal.

A Primeira coisa que um daemon deve fazer é um fork(), fazendo o fork() como a baixo você desassocia o programa do terminal.

    use warnings;
    use strict;

    use POSIX qw(setsid);

    my $pid = fork()
        or die "problema no fork";

    if ( $pid > 0 ) {
        exit;
    }
	...

A função fork() cria um novo processo executando seu programa, efetivamente dividindo ele em dois processos independentes: o pai e o filho. A função retorna 0 para o filho e um número maior do que 1 para o processo pai - o process id, ou PID, do filho. Caso alguma coisa dê errado, o fork() retorna undef.

No código acima, chamamos o fork() e finalizamos o processo pai. Assim nosso processo pode executar de forma independente, sem associações a outros processos em execução - um órfão.

O 2º Mandamento

Tornar-se o líder da sessão e o líder do sessão do grupo.

Processos órfãos podem ser eliminados pelo sistema operacional. Para evitar isso, chamamos a função POSIX setsid(), que executa o programa em uma nova sessão. A partir desse ponto, o processo filho possui um SID exclusivo do kernel.

    use warnings;
    use strict;

    use POSIX qw(setsid);

    my $pid = fork()
        or die "problema no fork";

    if ( $pid > 0 ) {
        exit;
    }
    setsid;




O 3º Mandamento

Mudar o diretório de execução para "/".

Se o diretório em que o daemon está executando for desmontado durante sua execução, ele vai simplesmente parar de rodar. Mudando o diretório para a raíz do sistema de arquivos, evitamos que isso aconteça.

    use warnings;
    use strict;

    use POSIX qw(setsid);

    my $pid = fork()
        or die "problema no fork";

    if ( $pid > 0 ) {
        exit;
    }
    setsid;
    chdir '/';




O 4º Mandamento

Mudar o umask para 0.

    use warnings;
    use strict;

    use POSIX qw(setsid);

    my $pid = fork()
        or die "problema no fork";

    if ( $pid > 0 ) {
        exit;
    }
    setsid;
    chdir '/';
    umask 0;

Para que o daemon possa escrever arquivos sem estar sujeito a restrições de permissão do processo pai, o umask deve ser mudado para 0 (zero).

O 5º Mandamento

Fechar todos os descritores padrões: STDOUT, STDIN e STDERR.

	use warnings;
    use strict;

    use POSIX qw(setsid);

    my $pid = fork()
        or die "problema no fork";

    if ( $pid > 0 ) {
        exit;
    }
    setsid;
    chdir '/';
    umask 0;
    open STDIN,  '<', $!; ' dev die null' open or stdout,>', '/dev/null' or die $!;
    open STDERR, '<', '/dev/null' or die $!;

Fazemos isso pelo simples fato do daemon não estar associado a nenhum terminal específico. Assim, não faz sentido que ele leia ou escreva em um.

O Último Mandamento

Usar algum tipo de log para ter informações sobre seu programa, já que fechamos todos descritores no mandamento acima.

    use warnings;
    use strict;

    use POSIX qw(setsid);

    my $pid = fork()
        or die "problema no fork";

    if ( $pid > 0 ) {
        exit;
    }
    setsid;
    chdir '/';
    umask 0;
    open STDIN,  '<', $!; ' dev die null' open or stdout,>', '/dev/null' or die $!;
    open STDERR, '<', $!; $log, ' dev die my null' open or>>', '/tmp/mylog.txt' or die $!;

Aqui, abrimos o arquivo /tmp/mylog.txt, e podemos escrever o que quisermos, de mensagens de erro a informações de execução.

Peão

Agora que já sabemos todas as regras para criar um daemon, podemos ter o nosso primeiro exemplo.

Abra um terminal e digite:

	$ touch /tmp/mylog.txt
	$ tail -f /tmp/mylog.txt

O comando acima cria e monitora o arquivo de texto /tmp/mylog.txt, que por enquanto está vazio. Agora vamos para a parte interessante, que é rodar o nosso programa-peão.

    use warnings;
    use strict;

    use POSIX qw(setsid);

    my $pid = fork()
        or die "problema no fork";

    if ( $pid > 0 ) {
        exit;
    }
    setsid;
    chdir '/';
    umask 0;
    open STDIN,  '<', $!; ' dev die null' open or stdout,>', '/dev/null' or die $!;
    open STDERR, '<', $!; $log, ' dev die my null' open or>>', '/tmp/mylog.txt' or die $!;

    for ( 1 .. 10 ) {
        print $log "teste\n";
    }
    exit;

Salve o arquivo e execute-o em outro terminal. Em seguida olhe o terminal em que o comando:

	tail -f /tmp/mylog.txt

estava executando. Perceba que o morto acordou, aqui temos o nosso primeiro exemplo de daemon.

Criando e Parando o Script

Você já percebeu como iniciamos ou interrompemos daemons como o do apache?

	/etc/init.d/apache2 start
	/etc/init.d/apache2 stop

Bem, você deve estar se perguntando, "Como o meu daemon faz isso?". Isso é o que vamos aprender agora!

A partir desse ponto, vamos assumir que você está usando uma distribuição Linux similar ao Debian (como o Ubuntu). Na prática, a maioria das distribuições Linux tem os atalhos que serão ensinados abaixo.

Meu Esqueleto

O Ubuntu tem o arquivo /etc/init.d/skeleton que é para você ter uma base do script de iniciar ou parar o seu daemon.

	sudo cp /etc/init.d/skeleton /etc/init.d/meudaemon
	sudo chmod +x /etc/init.d/meudaemon

Vamos fazer as mudanças necessárias no esqueleto:

	PATH=/sbin:/usr/sbin:/bin:/usr/bin
	DESC="Descricao do nosso servico"
	NAME=nomedoexecutavel
	DAEMON=/usr/bin/$NAME
	DAEMON_ARGS="--options args"
	PIDFILE=/var/run/$NAME.pid
	SCRIPTNAME=/etc/init.d/$NAME

Mude a descrição para o que você preferir, e a variável $NAME para o nome exato do seu daemon.

	PATH=/sbin:/usr/sbin:/bin:/usr/bin
	DESC="Super incrível daemon"
	NAME=mudaemon
	DAEMON=/usr/bin/$NAME
	DAEMON_ARGS="--options args"
	PIDFILE=/var/run/$NAME.pid
	SCRIPTNAME=/etc/init.d/$NAME

Nosso daemon

Vamos criar um espelho para o site da SPPM, http://sao-paulo.pm.org/principal. A única coisa de diferente nesse script, é que vamos criar um arquivo com o nosso pid.

	#!/usr/bin/perl
    use strict;
    use warnings;
    use POSIX qw(setsid);
    use File::Pid;
    use LWP::Simple;

	$| = 1; #auto flush

    my $nome        = "meudaemon";
    my $pidlocation = "/var/run/$nome.pid";

    # daemonize
    my $pid = fork()
        or die "problema no fork";

    if ( $pid > 0 ) {
        exit;
    }
    setsid;
    chdir '/';
    umask 0;
    open STDIN,  '<', $!; ' dev die null' open or stdout,>', '/dev/null' or die $!;
    open STDERR, '<', # $!; $log, ' dev die log my null' open or>>', "/var/log/$nome/log.txt" or die $!;

    # Criar o pid no /var/run/
    my $pidfile = File::Pid->new( { file => $pidlocation, } );

    # Se não conseguir escrever erro e morrer
    $pidfile->write
        or die "Erro criando o arquivo de PID, /dev/null: $!";

    while (1) {
        if ( mirror( "http://sao-paulo.pm.org/principal", "/tmp/meuespelho" ) == 200 ) {
            print $log "Espelho criado com sucesso\n";
        }
        else {
            print $log "Espelho não foi criado com sucesso\n";
        }
        sleep(1);
    }

Bem, salve o script como "meudaemon" e dê permissão de execução.

	chmod +x meudaemon

Copie o arquivo para o diretório /usr/sbin.

	sudo cp meudaemon /usr/sbin/

Agora vamos iniciar o nosso daemon.

	$ /etc/init.d/meudaemon start
	$ pgrep meudaemon
	17803
	$ cat /tmp/meuespelho
	
	
		
		
	...

Pronto, já sabemos o básico para criar um daemon.

Paiol

A linguagem Perl, tem um imenso repositório de código reutilizável. A seguir segue uma lista de alguns módulos específicos para daemons:

http://search.cpan.org/~deti/Proc-Daemon-0.07/lib/Proc/Daemon.pod

http://search.cpan.org/~markov/Any-Daemon-0.10/lib/Any/Daemon.pod

http://search.cpan.org/~mschilli/App-Daemon-0.11/Daemon.pm

http://search.cpan.org/~rokr/Daemon-Daemonize-0.0052/lib/Daemon/Daemonize.pm

Existem outras centenas de módulos que também podem lhe auxiliar . Além de ter código pronto, você tem documentação e código testado. Portando não se desgaste fazendo tudo manualmente, seja preguiçoso.

Não deixe que o daemon vire um demônio, use o CPAN :)

Agradecimentos

Thiago Rondon O grande organizador do Equinócio desde o começo, e que se não fosse por ele não teria o equinócio.

Breno G. Oliveira Contribuiu com ideias além de revisar o artigo.

Alexei Znamensky Contribuiu com ideias além de revisar o artigo.

Blabos de Bleble Revisou o artigo.

Referências

Autor

Daniel de Oliveira Mantovani

blog comments powered by Disqus