Os invólucros fork()
e vfork()
na glibc são implementados por meio da chamada do sistema clone()
. Para entender melhor o relacionamento entre fork()
e clone()
, devemos considerar a relação entre processos e threads no Linux.
Tradicionalmente, fork()
publica todos os recursos pertencentes ao processo pai e atribui a cópia ao processo filho. Essa abordagem incorre em uma sobrecarga considerável, o que pode ser por nada se a criança chamar imediatamente exec()
. No Linux, fork()
utiliza as páginas copy-on-write para atrasar ou evitar a cópia dos dados que podem ser compartilhados entre os processos pai e filho. Assim, a única sobrecarga incorrida durante um% normal fork()
é a cópia das tabelas de páginas pai e a atribuição de uma estrutura exclusiva do descritor de processo, task_struct
, para o filho.
O Linux também tem uma abordagem excepcional para threads. No Linux, os threads são meramente processos comuns que compartilham alguns recursos com outros processos. Essa é uma abordagem radicalmente diferente para os encadeamentos em comparação com outros sistemas operacionais, como o Windows ou o Solaris, em que processos e encadeamentos são tipos totalmente diferentes de animais. No Linux, cada thread tem um task_struct
comum que por si só é configurado de tal forma que compartilha certos recursos, como um espaço de endereço, com o processo pai.
O parâmetro flags
da chamada de sistema clone()
inclui um conjunto de sinalizadores que indicam quais recursos, se houver, devem ser compartilhados pelos processos pai e filho. Processos e encadeamentos são criados por meio de clone()
, a única diferença é o conjunto de sinalizadores passado para clone()
.
Um fork()
normal pode ser implementado como:
clone(SIGCHLD, 0);
Isso cria uma tarefa que não compartilha nenhum recurso com seu pai e está configurada para enviar o sinal SIGCHLD
de terminação para o pai quando ele sai.
Por outro lado, uma tarefa que compartilha o espaço de endereço, recursos do sistema de arquivos, descritores de arquivo e manipuladores de sinais com o pai, em outras palavras, um thread , pode ser criado com:
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);
vfork()
, por sua vez, é implementado por meio de um sinalizador CLONE_VFORK
separado, o que fará com que o processo pai seja suspenso até que o processo filho o acorde por meio de um sinal. O filho será o único thread de execução no namespace do pai, até chamar exec()
ou exits. A criança não tem permissão para escrever na memória. A chamada clone()
correspondente pode ser a seguinte:
clone(CLONE_VFORK | CLONE_VM | SIGCHLD, 0)
A implementação de sys_clone()
é específica da arquitetura, mas a maior parte do trabalho acontece em do_fork()
definido em kernel/fork.c
. Essa função chama o static clone_process()
, que cria um novo processo como uma cópia do pai, mas ainda não o inicia. clone_process()
copia os registradores, atribui um PID à nova tarefa e publica ou compartilha partes apropriadas do ambiente de processo, conforme especificado pelo clone flags
. Quando clone_process()
retornar, do_clone()
irá ativar o processo recém-criado e agendá-lo para ser executado.