Criando um daemon no Linux

110

No Linux, quero adicionar um daemon que não pode ser interrompido e que monitora as mudanças no sistema de arquivos. Se alguma mudança for detectada, ele deve escrever o caminho para o console onde foi iniciado mais uma nova linha.

Eu já tenho o código de alteração do sistema de arquivos quase pronto, mas não consigo descobrir como criar um daemon.

Meu código é daqui: http://www.yolinux.com/TUTORIALS/ForkExecProcesses.html

O que fazer depois da bifurcação?

int main (int argc, char **argv) {

  pid_t pID = fork();
  if (pID == 0)  {              // child
          // Code only executed by child process    
      sIdentifier = "Child Process: ";
    }
    else if (pID < 0) {
        cerr << "Failed to fork" << endl;
        exit(1);
       // Throw exception
    }
    else                                   // parent
    {
      // Code only executed by parent process

      sIdentifier = "Parent Process:";
    }       

    return 0;
}
chrisMe
fonte
1
Possível duplicata: stackoverflow.com/q/5384168/1076451
Chimera
1
possível duplicata de: stackoverflow.com/questions/5384168/… para a parte daemonize, stackoverflow.com/questions/931093/… para o monitoramento do sistema de arquivos
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Se você não precisa de conformidade com POSIX, pode estar interessado em inotifyAPI. Veja: inotify_init, inotify_add_watch, inotify_rm_watch.
patryk.beza

Respostas:

216

No Linux, quero adicionar um daemon que não pode ser interrompido e que monitora as mudanças no sistema de arquivos. Se alguma mudança for detectada, ele deve escrever o caminho para o console onde foi iniciado + uma nova linha.

Daemons funcionam em segundo plano e (geralmente ...) não pertencem a um TTY, por isso você não pode usar stdout / stderr da maneira que provavelmente deseja. Normalmente, um daemon syslog ( syslogd ) é usado para registrar mensagens em arquivos (depuração, erro, ...).

Além disso, existem algumas etapas necessárias para daemonizar um processo.


Se bem me lembro, essas etapas são:

  • bifurque o processo pai e deixe-o terminar se a bifurcação for bem-sucedida -> Como o processo pai foi encerrado, o processo filho agora é executado em segundo plano.
  • setsid - Cria uma nova sessão. O processo de chamada se torna o líder da nova sessão e o líder do grupo de processo do novo grupo de processo. O processo agora está desconectado de seu terminal de controle (CTTY).
  • Sinais de captura - ignora e / ou controla os sinais.
  • bifurque novamente e deixe o processo pai terminar para garantir que você se livre do processo líder da sessão. (Apenas os líderes da sessão podem obter um TTY novamente.)
  • chdir - Altera o diretório de trabalho do daemon.
  • umask - Altera a máscara do modo de arquivo de acordo com as necessidades do daemon.
  • fechar - fecha todos os descritores de arquivo abertos que podem ser herdados do processo pai.

Para lhe dar um ponto de partida: Observe este código-esqueleto que mostra as etapas básicas. Este código agora também pode ser bifurcado no GitHub: esqueleto básico de um daemon do Linux

/*
 * daemonize.c
 * This example daemonizes a process, writes a few log messages,
 * sleeps 20 seconds and terminates afterwards.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <syslog.h>

static void skeleton_daemon()
{
    pid_t pid;

    /* Fork off the parent process */
    pid = fork();

    /* An error occurred */
    if (pid < 0)
        exit(EXIT_FAILURE);

    /* Success: Let the parent terminate */
    if (pid > 0)
        exit(EXIT_SUCCESS);

    /* On success: The child process becomes session leader */
    if (setsid() < 0)
        exit(EXIT_FAILURE);

    /* Catch, ignore and handle signals */
    //TODO: Implement a working signal handler */
    signal(SIGCHLD, SIG_IGN);
    signal(SIGHUP, SIG_IGN);

    /* Fork off for the second time*/
    pid = fork();

    /* An error occurred */
    if (pid < 0)
        exit(EXIT_FAILURE);

    /* Success: Let the parent terminate */
    if (pid > 0)
        exit(EXIT_SUCCESS);

    /* Set new file permissions */
    umask(0);

    /* Change the working directory to the root directory */
    /* or another appropriated directory */
    chdir("/");

    /* Close all open file descriptors */
    int x;
    for (x = sysconf(_SC_OPEN_MAX); x>=0; x--)
    {
        close (x);
    }

    /* Open the log file */
    openlog ("firstdaemon", LOG_PID, LOG_DAEMON);
}
int main()
{
    skeleton_daemon();

    while (1)
    {
        //TODO: Insert daemon code here.
        syslog (LOG_NOTICE, "First daemon started.");
        sleep (20);
        break;
    }

    syslog (LOG_NOTICE, "First daemon terminated.");
    closelog();

    return EXIT_SUCCESS;
}


  • Compile o código: gcc -o firstdaemon daemonize.c
  • Inicie o daemon: ./firstdaemon
  • Verifique se tudo está funcionando corretamente: ps -xj | grep firstdaemon

  • A saída deve ser semelhante a esta:

+ ------ + ------ + ------ + ------ + ----- + ------- + ------ + ------ + ------ + ----- +
| PPID | PID | PGID | SID | TTY | TPGID | STAT | UID | TIME | CMD |
+ ------ + ------ + ------ + ------ + ----- + ------- + ------ + ------ + ------ + ----- +
| 1 | 3387 | 3386 | 3386 | ? | -1 | S | 1000 | 0:00 | ./ |
+ ------ + ------ + ------ + ------ + ----- + ------- + ------ + ------ + ------ + ----- +

O que você deve ver aqui é:

  • O daemon não tem terminal de controle ( TTY =? )
  • O ID do processo pai ( PPID ) é 1 (o processo init)
  • O PID! = SID, o que significa que nosso processo NÃO é o líder da sessão
    (por causa da segunda bifurcação ())
  • Porque PID! = SID nosso processo não pode assumir o controle de um TTY novamente

Lendo o syslog:

  • Localize seu arquivo syslog. O meu está aqui:/var/log/syslog
  • Faça um: grep firstdaemon /var/log/syslog

  • A saída deve ser semelhante a esta:

  firstdaemon [3387]: Primeiro daemon iniciado.
  firstdaemon [3387]: Primeiro daemon terminado.


Uma nota: na realidade, você também gostaria de implementar um manipulador de sinal e configurar o registro corretamente (arquivos, níveis de registro ...).

Leitura adicional:

Pascal Werkl
fonte
Uau, obrigado! Isso é ótimo. Então eu tenho que colocar meu código no loop while e pronto?
chrisMe
Basicamente, sim. Mas este código é apenas um exemplo. Depende inteiramente do que você deseja alcançar usando um processo daemon. Não deixe de ler esta resposta também: @Edwin
Pascal Werkl
1
Em vez do segundo fork(), por que não apenas usar setsid()?
Quimera
1
Observe que a sigaction()função fornece um mecanismo mais abrangente e confiável para controlar sinais; novos aplicativos devem usar em sigaction()vez de signal().
patryk.beza
4
Deve-se notar aos telespectadores que este método é a "velha" maneira. A nova maneira recomendada de criar um daemon é com o "daemon de novo estilo" encontrado aqui: 0pointer.de/public/systemd-man/daemon.html#New-Style%20Daemons ou
Starlord
30

man 7 daemondescreve como criar daemon em grandes detalhes. Minha resposta é apenas um trecho deste manual.

Existem pelo menos dois tipos de daemons:

  1. daemons SysV tradicionais ( estilo antigo ),
  2. daemons do systemd ( novo estilo ).

SysV Daemons

Se você estiver interessado no daemon SysV tradicional , deverá implementar as seguintes etapas :

  1. Feche todos os descritores de arquivo abertos, exceto entrada , saída e erro padrão (ou seja, os três primeiros descritores de arquivo 0, 1, 2). Isso garante que nenhum descritor de arquivo passado acidentalmente permaneça no processo do daemon. No Linux, isso é melhor implementado por meio da iteração /proc/self/fd, com um fallback da iteração do descritor de arquivo 3 para o valor retornado por getrlimit()for RLIMIT_NOFILE.
  2. Redefina todos os manipuladores de sinal para seus padrões. A melhor maneira de fazer isso é iterar os sinais disponíveis até o limite de _NSIGe redefini-los para SIG_DFL.
  3. Reinicialize a máscara de sinal usando sigprocmask().
  4. Limpe o bloco de ambiente, removendo ou redefinindo as variáveis ​​de ambiente que podem impactar negativamente o tempo de execução do daemon.
  5. Ligue fork()para criar um processo em segundo plano.
  6. Na criança, chame setsid()para desconectar de qualquer terminal e crie uma sessão independente .
  7. Na criança, chame fork()novamente, para garantir que o daemon nunca possa readquirir um terminal novamente.
  8. Chame exit()o primeiro filho, de modo que apenas o segundo filho (o processo real daemon) permaneça. Isso garante que o processo daemon seja redirecionado para init / PID 1, como todos os daemons deveriam ser.
  9. No processo daemon, conecte /dev/null- se à entrada , saída e erro padrão .
  10. No processo daemon, redefina o umaskpara 0, de modo que os modos de arquivo passados ​​para open(), mkdir()e outros , controlem diretamente o modo de acesso dos arquivos e diretórios criados.
  11. No processo do daemon, altere o diretório atual para o diretório raiz ( /), a fim de evitar que o daemon bloqueie involuntariamente os pontos de montagem de serem desmontados.
  12. No processo do daemon, escreva o PID do daemon (conforme retornado por getpid()) em um arquivo PID, por exemplo /run/foobar.pid(para um daemon hipotético "foobar") para garantir que o daemon não possa ser iniciado mais de uma vez. Isso deve ser implementado de forma livre de corridas para que o arquivo PID seja atualizado somente quando for verificado ao mesmo tempo que o PID previamente armazenado no arquivo PID não existe mais ou pertence a um processo estrangeiro.
  13. No processo daemon, cancele os privilégios, se possível e aplicável.
  14. No processo daemon, notifique o processo original iniciado de que a inicialização foi concluída. Isso pode ser implementado por meio de um canal sem nome ou canal de comunicação semelhante, criado antes do primeiro fork()e, portanto, disponível no processo original e no daemon.
  15. Ligue exit()no processo original. O processo que chamou o daemon deve ser capaz de confiar que isso exit()aconteça após a inicialização ser concluída e todos os canais de comunicação externos estiverem estabelecidos e acessíveis.

Observe este aviso:

A daemon()função BSD não deve ser usada, pois ela implementa apenas um subconjunto dessas etapas.

Um daemon que precisa fornecer compatibilidade com sistemas SysV deve implementar o esquema apontado acima. No entanto, é recomendável tornar esse comportamento opcional e configurável por meio de um argumento de linha de comando para facilitar a depuração, bem como para simplificar a integração em sistemas usando o systemd.

Observe que daemon()não é compatível com POSIX .


Daemons de novo estilo

Para daemons de novo estilo, as seguintes etapas são recomendadas:

  1. Se SIGTERMfor recebido, desligue o daemon e saia de forma limpa.
  2. Se SIGHUPfor recebido, recarregue os arquivos de configuração, se for o caso.
  3. Forneça um código de saída correto do processo daemon principal, já que ele é usado pelo sistema init para detectar erros e problemas de serviço. Recomenda-se seguir o esquema de código de saída conforme definido nas recomendações LSB para scripts de inicialização SysV .
  4. Se possível e aplicável, exponha a interface de controle do daemon por meio do sistema D-Bus IPC e pegue um nome de barramento como última etapa da inicialização.
  5. Para integração no systemd, forneça um arquivo de unidade .service que carregue informações sobre como iniciar, parar e manter o daemon. Veja para detalhes.systemd.service(5)
  6. Tanto quanto possível, conte com a funcionalidade do sistema init para limitar o acesso do daemon a arquivos, serviços e outros recursos, ou seja, no caso do systemd, conte com o controle de limite de recursos do systemd em vez de implementar o seu próprio, conte com a eliminação de privilégios do systemd código em vez de implementá-lo no daemon e semelhantes. Veja systemd.exec(5)os controles disponíveis.
  7. Se o D-Bus for usado, torne seu daemon bus-ativável, fornecendo um arquivo de configuração de ativação de serviço D-Bus . Isso tem várias vantagens: seu daemon pode ser iniciado lentamente sob demanda; ele pode ser iniciado em paralelo a outros daemons que o requerem - o que maximiza a paralelização e a velocidade de inicialização ; seu daemon pode ser reiniciado em caso de falha sem perder nenhuma solicitação de barramento, pois o barramento enfileira solicitações de serviços ativáveis. Veja abaixo os detalhes.
  8. Se o seu daemon fornece serviços para outros processos locais ou clientes remotos por meio de um soquete, ele deve ser ativado por soquete seguindo o esquema indicado abaixo . Como a ativação do D-Bus, isso permite a inicialização de serviços sob demanda, bem como permite a paralelização aprimorada da inicialização de serviço. Além disso, para protocolos sem estado (como syslog, DNS), um daemon que implementa a ativação baseada em soquete pode ser reiniciado sem perder uma única solicitação. Veja abaixo os detalhes.
  9. Se aplicável, um daemon deve notificar o sistema init sobre a conclusão da inicialização ou atualizações de status por meio da sd_notify(3)interface.
  10. Em vez de usar a syslog()chamada para registrar diretamente no serviço syslog do sistema, um daemon de novo estilo pode escolher simplesmente registrar o erro padrão via fprintf(), que é então encaminhado para o syslog pelo sistema init. Se os níveis de log forem necessários, eles podem ser codificados prefixando linhas de log individuais com strings como "<4>" (para nível de log 4 "AVISO" no esquema de prioridade do syslog), seguindo um estilo semelhante ao do sistema de printk()nível do kernel Linux . Para obter detalhes, consulte sd-daemon(3)e systemd.exec(5).

Para saber mais, leia na íntegra man 7 daemon.

patryk.beza
fonte
11

Você não pode criar um processo no Linux que não possa ser eliminado. O usuário root (uid = 0) pode enviar um sinal para um processo, e há dois sinais que não podem ser capturados, SIGKILL = 9, SIGSTOP = 19. E outros sinais (quando não capturados) também podem resultar no encerramento do processo.

Você pode querer uma função daemonize mais geral, onde você pode especificar um nome para seu programa / daemon e um caminho para executar seu programa (talvez "/" ou "/ tmp"). Você também pode querer fornecer arquivo (s) para stderr e stdout (e possivelmente um caminho de controle usando stdin).

Aqui estão os itens necessários:

#include <stdio.h>    //printf(3)
#include <stdlib.h>   //exit(3)
#include <unistd.h>   //fork(3), chdir(3), sysconf(3)
#include <signal.h>   //signal(3)
#include <sys/stat.h> //umask(3)
#include <syslog.h>   //syslog(3), openlog(3), closelog(3)

E aqui está uma função mais geral,

int
daemonize(char* name, char* path, char* outfile, char* errfile, char* infile )
{
    if(!path) { path="/"; }
    if(!name) { name="medaemon"; }
    if(!infile) { infile="/dev/null"; }
    if(!outfile) { outfile="/dev/null"; }
    if(!errfile) { errfile="/dev/null"; }
    //printf("%s %s %s %s\n",name,path,outfile,infile);
    pid_t child;
    //fork, detach from process group leader
    if( (child=fork())<0 ) { //failed fork
        fprintf(stderr,"error: failed fork\n");
        exit(EXIT_FAILURE);
    }
    if (child>0) { //parent
        exit(EXIT_SUCCESS);
    }
    if( setsid()<0 ) { //failed to become session leader
        fprintf(stderr,"error: failed setsid\n");
        exit(EXIT_FAILURE);
    }

    //catch/ignore signals
    signal(SIGCHLD,SIG_IGN);
    signal(SIGHUP,SIG_IGN);

    //fork second time
    if ( (child=fork())<0) { //failed fork
        fprintf(stderr,"error: failed fork\n");
        exit(EXIT_FAILURE);
    }
    if( child>0 ) { //parent
        exit(EXIT_SUCCESS);
    }

    //new file permissions
    umask(0);
    //change to path directory
    chdir(path);

    //Close all open file descriptors
    int fd;
    for( fd=sysconf(_SC_OPEN_MAX); fd>0; --fd )
    {
        close(fd);
    }

    //reopen stdin, stdout, stderr
    stdin=fopen(infile,"r");   //fd=0
    stdout=fopen(outfile,"w+");  //fd=1
    stderr=fopen(errfile,"w+");  //fd=2

    //open syslog
    openlog(name,LOG_PID,LOG_DAEMON);
    return(0);
}

Aqui está um programa de amostra, que se torna um daemon, fica pendurado e depois sai.

int
main()
{
    int res;
    int ttl=120;
    int delay=5;
    if( (res=daemonize("mydaemon","/tmp",NULL,NULL,NULL)) != 0 ) {
        fprintf(stderr,"error: daemonize failed\n");
        exit(EXIT_FAILURE);
    }
    while( ttl>0 ) {
        //daemon code here
        syslog(LOG_NOTICE,"daemon ttl %d",ttl);
        sleep(delay);
        ttl-=delay;
    }
    syslog(LOG_NOTICE,"daemon ttl expired");
    closelog();
    return(EXIT_SUCCESS);
}

Observe que SIG_IGN indica para capturar e ignorar o sinal. Você pode construir um manipulador de sinal que pode registrar o recebimento do sinal e definir sinalizadores (como um sinalizador para indicar desligamento normal).

ChuckCottrill
fonte
8

Tente usar a daemonfunção:

#include <unistd.h>

int daemon(int nochdir, int noclose);

Na página de manual :

A função daemon () é para programas que desejam se desconectar do terminal de controle e rodar em segundo plano como daemons do sistema.

Se nochdir for zero, daemon () muda o diretório de trabalho atual do processo de chamada para o diretório raiz ("/"); caso contrário, o diretório de trabalho atual não será alterado.

Se noclose for zero, daemon () redireciona a entrada padrão, a saída padrão e o erro padrão para / dev / null; caso contrário, nenhuma alteração será feita nesses descritores de arquivo.

Weiyin
fonte
2
Observe que o daemon(7)manual menciona etapas para criar daemon e avisa que: A daemon()função BSD não deve ser usada, pois implementa apenas um subconjunto dessas etapas. daemonA função apareceu pela primeira vez no 4.4BSD e não é compatível com POSIX .
patryk.beza
2
Observe também que o aviso sobre o uso de daemon () está na seção SysV de estilo antigo da página do manual daemon (7) . O uso de daemon () não é desencorajado para o systemd.
Greg McPherran
7

Posso parar no primeiro requisito "Um daemon que não pode ser interrompido ..."

Não é possível meu amigo; entretanto, você pode conseguir o mesmo com uma ferramenta muito melhor, um módulo de kernel.

http://www.infoq.com/articles/inotify-linux-file-system-event-monitoring

Todos os daemons podem ser interrompidos. Alguns são interrompidos mais facilmente do que outros. Mesmo um par de daemons com o parceiro em espera, renascendo o parceiro se perdido, pode ser interrompido. Você apenas tem que trabalhar um pouco mais para isso.

Edwin Buck
fonte
7
Acho que ao dizer "Um daemon que não pode ser interrompido", o autor realmente quer dizer que o daemon está sempre em execução em segundo plano quando a sessão é encerrada.
FaceBro
6

Se seu aplicativo for um dos seguintes:

{
  ".sh": "bash",
  ".py": "python",
  ".rb": "ruby",
  ".coffee" : "coffee",
  ".php": "php",
  ".pl" : "perl",
  ".js" : "node"
}

e você não se importa com uma dependência do NodeJS, instale o NodeJS e:

npm install -g pm2

pm2 start yourapp.yourext --name "fred" # where .yourext is one of the above

pm2 start yourapp.yourext -i 0 --name "fred" # run your app on all cores

pm2 list

Para manter todos os aplicativos em execução na reinicialização (e daemonise pm2):

pm2 startup

pm2 save

Agora você pode:

service pm2 stop|restart|start|status

(também permite que você observe facilmente as alterações de código no diretório do seu aplicativo e reinicie automaticamente o processo do aplicativo quando uma alteração de código acontecer)

danday74
fonte
2
Isso não tem nada a ver com C.
melpomene
4
Agradeço que haja uma tag C. No entanto, OP não menciona um requisito relativo a C na pergunta. O título é criar um demônio no Linux. Esta resposta satisfaz isso.
danday74
1
Oh, você está certo. É marcado como C, mas o requisito real é C ++ (conforme evidenciado pelo código do OP e o artigo vinculado).
melpomene
3

Ao chamar fork (), você criou um processo filho. Se a bifurcação for bem-sucedida (bifurcação retornou um PID diferente de zero), a execução continuará a partir desse ponto de dentro do processo filho. Nesse caso, queremos sair normalmente do processo pai e, em seguida, continuar nosso trabalho no processo filho.

Talvez isso ajude: http://www.netzmafia.de/skripten/unix/linux-daemon-howto.html

Doug Morrow
fonte
2

Um daemon é apenas um processo em segundo plano. Se você deseja iniciar seu programa quando o sistema operacional inicializa, no linux, você adiciona seu comando de inicialização a /etc/rc.d/rc.local (executa após todos os outros scripts) ou /etc/startup.sh

No Windows, você cria um serviço, registra o serviço e então o configura para iniciar automaticamente na inicialização em administração -> painel de serviços.

Magn3s1um
fonte
1
Obrigado. Portanto, não há diferença entre um "daemon" e apenas um programa normal? Não quero que seja fechado facilmente.
chrisMe
1
Não, um daemon é apenas um processo em segundo plano. Mais especificamente, você bifurca de um pai, executa o processo filho e encerra o pai (de forma que não haja acesso de terminal ao programa). isso não é realmente necessário para ser um "daemon": en.wikipedia.org/wiki/Daemon_(computing)
Magn3s1um
1

Daemon Template

Eu escrevi um modelo de daemon seguindo o daemon de novo estilo: link

Você pode encontrar o código do modelo completo no GitHub: aqui

Main.cpp

// This function will be called when the daemon receive a SIGHUP signal.
void reload() {
    LOG_INFO("Reload function called.");
}

int main(int argc, char **argv) {
    // The Daemon class is a singleton to avoid be instantiate more than once
    Daemon& daemon = Daemon::instance();
    // Set the reload function to be called in case of receiving a SIGHUP signal
    daemon.setReloadFunction(reload);
    // Daemon main loop
    int count = 0;
    while(daemon.IsRunning()) {
        LOG_DEBUG("Count: ", count++);
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    LOG_INFO("The daemon process ended gracefully.");
}

Daemon.hpp

class Daemon {
    public:

    static Daemon& instance() {
        static Daemon instance;
        return instance;
    }

    void setReloadFunction(std::function<void()> func);

    bool IsRunning();

    private:

    std::function<void()> m_reloadFunc;
    bool m_isRunning;
    bool m_reload;

    Daemon();
    Daemon(Daemon const&) = delete;
    void operator=(Daemon const&) = delete;

    void Reload();

    static void signalHandler(int signal);
};

Daemon.cpp

Daemon::Daemon() {
    m_isRunning = true;
    m_reload = false;
    signal(SIGINT, Daemon::signalHandler);
    signal(SIGTERM, Daemon::signalHandler);
    signal(SIGHUP, Daemon::signalHandler);
}

void Daemon::setReloadFunction(std::function<void()> func) {
    m_reloadFunc = func;
}

bool Daemon::IsRunning() {
    if (m_reload) {
        m_reload = false;
        m_reloadFunc();
    }
    return m_isRunning;
}

void Daemon::signalHandler(int signal) {
    LOG_INFO("Interrup signal number [", signal,"] recived.");
    switch(signal) {
        case SIGINT:
        case SIGTERM: {
            Daemon::instance().m_isRunning = false;
            break;
        }
        case SIGHUP: {
            Daemon::instance().m_reload = true;
            break;
        }
    }
}

daemon-template.service

[Unit]
Description=Simple daemon template
After=network.taget

[Service]
Type=simple
ExecStart=/usr/bin/daemon-template --conf_file /etc/daemon-template/daemon-tenplate.conf
ExecReload=/bin/kill -HUP $MAINPID
User=root
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=daemon-template

[Install]
WantedBy=multi-user.target
Fabiano Traple
fonte