Skip to content

NakedSolidSnake/Raspberry_fork_exec_daemon

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

fork(), exec() e daemon() System Call

Tópicos

Introdução

O artigo anterior descreveu a biblioteca hardware que é necessária para viabilizar o desenvolvimento dos exemplos, com o desenvolvimento da camada abstração concluída e instalada, e as pendências resolvidas, ainda necessitamos de alguns conceitos para prosseguir que são a combinação de fork e exec, e a definição do que é um daemon, que serão de vital importância para os próximos artigos, que seguirão esse modelo.

fork()

O fork() é um system call capaz de criar um novo processo denominado filho, que é uma cópia exata do processo original denominado pai. Para utilizar o fork() é necessário incluir os seguintes includes:

# include <sys/types.h>
# include <unistd.h>

pid_t fork (void);

Para exemplificar vamos criar uma aplicação muito conhecido por todos, o famoso hello world, a seguir vemos o seguinte código fonte que tem a finalidade de mostrar no console a string hello world:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main(void)
{
    fork();
    printf("hello world.\n");
    return 0;
}

O output desse código será:

hello world.
hello world.

O resultado do código anterior resultou em uma grande confusão agora é necessário explicar o que ocorre por de trás das cortinas.

  • Pessoa : Duas strings hello world?
  • Alguém : Isso mesmo, uma mensagem do processo original e outro do processo clone.
  • Pessoa : Mas como é possível haver um clone?
  • Alguém : Não acredita? Tudo bem então vamos provar.
  • Pessoa : Mas como fazer isso?
  • Alguém : Bom para isso precisamos encontrar evidências sobre esse mistério.
  • Pessoa : Como pode apresentar duas mensagens sendo que o programa é um só? E somente existe uma ocorrência de printf. Bizarro.
  • Alguém : Não nos resta alternativa, precisamos interrogar, vamos perguntar para o programa quem é ele com a função getpid.
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main(void)
{
    fork();
    printf("Quem eh voce? Eu sou o processo de pid %d.\n", getpid());
    return 0;
}

Ao executar é possível ver a saída com o resultado:

Quem eh voce? Eu sou o processo de pid 19489.
Quem eh voce? Eu sou o processo de pid 19490.

Oh não, isso deve ser um pesadelo é o ataque dos clones. Hey vai com calma não é isso que você está pensando, isso é só um Jutsu Clone das sombras, é uma técnica para poder dividir o trabalho. Bom isso seria útil para realizar as tarefas de casa. A figura abaixo demonstra o que ocorre durante o fork:

Como apresentado na figura é possível notar que o fluxo é ramificado após a execução do fork, para verificar quem é quem, com o retorno do fork é possível distiguir qual processo é qual. Vejamos mais um exemplo para demonstrar o resultado do fork:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main(void)
{
    pid_t is_clone = fork();
    
    if(is_clone == -1)
    {
        printf("Não foi possivel gerar um clone.\n");
    }
    else if( is_clone == 0)
    {
        printf("O que eu estou fazendo aqui, cara fui clonado.\n");
    }else
    {
        printf("Eu sou o verdadeiro.\n");
    }
    return 0;
}

A saída fica assim :

Eu sou o verdadeiro.
O que eu estou fazendo aqui, cara fui clonado

Como pode ser observado o clone retorna o valor 0, o original recebe o PID do clone, e se houver erro o retorno é -1.

Mas para que serve isso? Para o nosso propósito precisamos apresentar mais um componente.

exec()

  • Clone : Cara não acredito?
  • Alguém : O que foi que aconteceu?
  • Clone: Eu não passo de um mero clone.
  • Alguém: Não cara, não diga isso você pode ser um clone mas você pode seguir o seu próprio caminho, ser o que quiser ser.
  • Clone: Sério?
  • Alguém: Sim você precisa somente de um contexto, e assim seguir o seu próprio caminho.
  • Clone: Nossa isso seria ótimo mas como eu faço isso?
  • Alguém: Temos um recurso bastante versátil conhecido como exec, com ele é possível carregar um outro programa, que trocará todo o contexto anterior, e você será promovido, dessa forma você pode ser que você quiser.

O exec é um system call capaz de carregar outro programa, trocando todo o contexto do programa que o invoca. O exec possui várias formas de ser utilizado e suas variações se devem ao passar do tempo programadores o adaptavam conforme suas necessidades resultando nessa quantidade de funções, para melhor atendê-lo verifique o man pages para maiores informações sobre o exec:

int execl(const char *path, const char *arg, .../* (char  *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);
int execle(const char *path, const char *arg, ... /*, (char *) NULL, char * const envp[]*/);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

Para exemplicar vamos criar uma outra aplicação

O programa a seguir é o responsável pela criação do clone. após clonado o programa clone decide que quer se tornar um contador e seguir carreira nessa área.

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

int main(void)
{
    pid_t is_clone = fork();
    
    if(is_clone == -1)
    {
        printf("Não foi possivel gerar um clone.\n");
    }
    else if( is_clone == 0)
    {
        printf("Eu sou o clone mas posso ser o que eu quiser.\n");
        printf("Vou virar um contador.\n");
        char *args[]={"./counter",NULL}; 
        execvp(args[0],args); 
        /* se der certo o programa não retorna */
    }else
    {
        printf("Eu sou o verdadeiro.\n");
    }
    return 0;
}

O programa a seguir apresenta em um novo momento, talvez um momento de realização para o clone, que mesmo sendo um clone pode decidir qual caminho trilhar, e decidiu virar um contador, muito bom meu caro amigo persiga o seu sonho.

#include <stdio.h>

/* Programa chamado pelo exec */

void delay(void)
{
    for(int i = 0; i < 100; i++)
        for(int j = 0; j < 1000; j++);
}

int main()
{
    for(int i = 0; i < 10; i++)
    {
        printf("%d ", i);
        delay();
    }

    printf("\n");
    return 0;
}

daemon()

  • Alguém: Agora como tudo foi esclarecido vamos ver o que é o daemon.
  • Pessoa: Demon? Que isso cara?
  • Alguém: Não Demon, mas sim daemon, que coisa não?

Basicamente daemon é uma instância que roda em segundo plano, sem a interação de STDIN, STDOUT e STDERR. Normalmente fornece algum tipo de serviço como por exemplo o sshd(security shell daemon) que ouve conexões usando protocolo ssh e atua como servidor para o protocolo, possui um ciclo de vida desde o power on da máquina até o shutdown(caso não haja segfault ;P). No tópico de fork, é gerado uma cópia do programa corrente, esse programa filho é uma espécie de daemon, pois roda em segundo plano após o fork. Para a criação de um daemon existe um roteiro onde alguns passos devem ser seguidos, e existe também uma system call que abstrai todo o processo de criação:

Criação pelo modelo tradicional

  • Gerar o clone do processo através do fork para que o processo rode em background
process_id = fork();
if(process_id < 0)
{
printf("fork falhou.\n");
exit(1);
}

if(process_id > 0)
{
printf("PID do processo filho %d\n", process_id);
exit(0);
}
  • Alterar as permissões de acesso, para esse caso o argumento 0 significa que as permissões serão herdadas
umask(0);
  • Criar uma nova sessão
sid = setsid();
if(sid < 0)
{
exit(1);
}
  • Configurar o diretório de trabalho
chdir("/");
  • Fechar a entrada padrão, a saída padrão e a saída de erro padrão
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
  • Executar o serviço proposto, que vai logar a contagem a cada um segundo
int i = 0;

  while(1)
  {
    call_log(i++);
    sleep(1);
  }

Aqui está a descrição completa do exemplo

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

void call_log(int counter)
{
    openlog("Daemon", LOG_PID | LOG_CONS , LOG_USER);
    syslog(LOG_INFO, "Counter : %d", counter);
    closelog();
}

int main(int argc, char* argv[])
{
  pid_t process_id = 0;
  pid_t sid = 0;

  process_id = fork();
  if(process_id < 0)
  {
    printf("fork falhou.\n");
    exit(1);
  }

  if(process_id > 0)
  {
    printf("PID do processo filho %d\n", process_id);
    exit(0);
  }

  umask(0);

  sid = setsid();
  if(sid < 0)
  {
    exit(1);
  }

  chdir("/");

  close(STDIN_FILENO);
  close(STDOUT_FILENO);
  close(STDERR_FILENO);


  int i = 0;

  while(1)
  {
    call_log(i++);
    sleep(1);
  }
  
  return 0;
}

Criação pela system call

A system call daemon capaz de abstrair tudo isso em uma única chamada

#include <unistd.h>

int daemon(int nochdir, int noclose);

O mesmo exemplo usando daemon system call

#include <stdio.h>
#include <syslog.h>
#include <unistd.h>
#include <stdbool.h>

void call_log(int counter)
{
    openlog("Daemon", LOG_PID | LOG_CONS , LOG_USER);
    syslog(LOG_INFO, "Counter : %d", counter);
    closelog();
}

int main(int argc, char *argv[])
{
    int i = 0;

    daemon(0, 0);    
    
    while(true)
    {
        call_log(i++);
        sleep(1);
    }
    return 0;
}

Conclusão

Neste artigo foi apresentado como se utiliza o fork atráves de alguns exemplos simples, e como utilizar o clone para invocar um outra aplicação utilizando o comando exec, dessa forma podemos criar serviços para prover algumas funcionalidades para o sistema na forma de daemon. No próximo artigo apresentarei o primeiro IPC o PIPE.

Referências

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published