Por que o vfork () deve ser usado quando o processo filho chama exec () ou exit () imediatamente após a criação?

11

Conceitos de sistema operacional e APUE dizem

Com vfork (), o processo pai é suspenso e o processo filho usa o espaço de endereço do pai. Como o vfork () não usa a cópia na gravação, se o processo filho alterar alguma página do espaço de endereço do pai, as páginas alteradas ficarão visíveis para o pai depois que ele for reiniciado. Portanto, vfork () deve ser usado com cuidado para garantir que o processo filho não modifique o espaço de endereço do pai.

O vfork () deve ser usado quando o processo filho chama exec () ou exit () imediatamente após a criação.

Como vou entender a última frase?

Quando um processo filho criado por vfork()chamadas exec(), não exec()modifica o espaço de endereço do processo pai, carregando o novo programa?

Quando um processo filho criado por vfork()chamadas exit(), exit()não modifica o espaço de endereço do processo pai ao finalizar o filho?

Eu tenho preferência para Linux.

Obrigado.

Tim
fonte

Respostas:

15

Quando um processo filho criado por vfork()chamadas exec(), não exec()modifica o espaço de endereço do processo pai, carregando o novo programa?

Não, exec()fornece um novo espaço de endereço para o novo programa; não modifica o espaço de endereço pai. Veja, por exemplo, a discussão das execfunções no POSIX e a página de execve()manual do Linux .

Quando um processo filho criado por vfork () chama exit (), exit () não modifica o espaço de endereço do processo pai ao finalizar o filho?

Poder simples exit()- ele executa ganchos de saída instalados pelo programa em execução (incluindo suas bibliotecas). vfork()é mais restritivo; Assim, no Linux, que exige o uso de _exit()que não chamar funções de limpeza da biblioteca C.

vfork()acabou sendo bastante difícil de acertar; foi removido nas versões atuais do padrão POSIX e posix_spawn()deve ser usado.

No entanto, a menos que você realmente sabe o que está fazendo, você deve não usar vfork()ou posix_spawn(); ater ao bom fork()e velho exec().

A página de manual do Linux vinculada acima fornece mais contexto:

No entanto, nos velhos tempos, fork(2) seria necessário fazer uma cópia completa do espaço de dados do chamador, muitas vezes desnecessariamente, uma vez que geralmente imediatamente depois disso exec(3)é feito. Assim, para maior eficiência, o BSD introduziu a vfork() chamada do sistema, que não copiava completamente o espaço de endereço do processo pai, mas emprestava a memória e o encadeamento de controle dos pais até que uma chamada execve(2)ou saída ocorresse. O processo pai foi suspenso enquanto o filho estava usando seus recursos. O uso de vfork()foi complicado: por exemplo, não modificar dados no processo pai dependia de saber quais variáveis ​​eram mantidas em um registro.

Stephen Kitt
fonte
Obrigado. "exec () fornece um novo espaço de endereço para o novo programa;" O comportamento normal do exec () é carregar um programa no espaço de endereço do processo? Não encontrei nos dois links onde ele cria um novo espaço de endereço, normalmente ou particularmente para o vfork ().
Tim
1
O engraçado é que o vfork () está vencendo praticamente tudo o mais agora. É ridiculamente mais rápido que o fork () quando você tem um gigabyte de memória gravável.
Joshua
2
Por favor, não diga às pessoas para usar posix_spawn. É significativamente mais difícil escrever o código correto usando do posix_spawnque com o antigo forke, se você tentar, pode se deparar com uma parede de tijolos por não haver uma ação ou atributo de arquivo que faça o que você precisa fazer entre forke exec. E não é garantido que tenha uma eficiência semelhante ao vfork, portanto nem sequer resolve o problema que as pessoas querem que ele resolva.
Zwol 15/1018
1
@ zwol: Esse é um péssimo conselho. Embora posix_spawnpossa faltar a funcionalidade desejada (você pode resolver isso por meio de um programa auxiliar intermediário, escrito em C ou em shell script inline-on-cmdline), qualquer tentativa de conseguir o que deseja vforkinvoca um comportamento indefinido perigoso. A especificação para vforknão permite que funções aleatórias de chamada configurem o estado que o filho herdará antes execvee tentativas de fazê-lo podem corromper o estado do pai.
R .. GitHub Pare de ajudar o gelo
1
@ Josué: Uma implementação moderna de posix_spawnexecuta aproximadamente o mesmo que vforkna maioria das condições. Aqueles em que há uma diferença tendem a ser exatamente os casos em que vforké altamente inseguro: onde existem manipuladores de sinal instalados que precisam impedir posix_spawna execução no filho antes do exec.
R .. GitHub Pare de ajudar o gelo
4

Quando você chama vfork(), um novo processo é criado e esse novo processo empresta a imagem do processo pai, com exceção da pilha. O processo filho recebe uma nova estrela de pilha, no entanto, não permite a returnpartir da função que chamou vfork().

Enquanto o filho estiver em execução, o processo pai será bloqueado, pois o filho emprestou o espaço de endereço do pai.

Independentemente do que você faz, tudo o que acessa apenas a pilha modifica apenas a pilha privada da criança. Se você modificar dados globais, isso modificará os dados comuns e, portanto, também afetará o pai.

Coisas que modificam dados globais são, por exemplo:

  • chamando malloc () ou free ()

  • usando stdio

  • modificando configurações de sinal

  • modificando variáveis ​​que não são locais para a função que chamou vfork().

  • ...

Depois que você liga _exit()(importante, nunca liga exit()), a criança é encerrada e o controle é devolvido aos pais.

Se você chamar qualquer função da exec*()família, um novo espaço de endereço será criado com o novo código do programa, novos dados e uma parte da pilha do pai (veja abaixo). Uma vez pronto, o filho não pede mais o espaço de endereço do filho, mas usa um próprio espaço de endereço.

O controle é devolvido ao pai, pois seu espaço de endereço não está mais sendo usado por outro processo.

Importante: No Linux, não há vfork()implementação real . O Linux é implementado com vfork()base no fork()conceito Copy on Write, introduzido pelo SunOS-4.0 em 1988. Para fazer os usuários acreditarem que usam vfork(), o Linux apenas configura dados compartilhados e suspende o pai enquanto o filho não chamava _exit()ou uma das exec*()funções.

Portanto, o Linux não se beneficia do fato de que um real vfork()não precisa configurar uma descrição do espaço de endereço para o filho no kernel. Isso resulta em um vfork()que não é mais rápido que fork(). Em sistemas que implementam um real vfork(), normalmente é 3x mais rápido que fork()e afeta o desempenho de shells que usam vfork()- ksh93, o recente Bourne Shelle csh.

A razão pela qual você nunca deve ligar exit()do vfork()filho ed é que exit()libera o stdio caso haja dados não liberados do momento antes da chamada vfork(). Isso pode causar resultados estranhos.

BTW: posix_spawn()é implementado em cima de vfork(), portanto, vfork()não será removido do sistema operacional. Foi mencionado que o Linux não usa vfork()para posix_spawn().

Para a pilha, há pouca documentação, aqui está o que a página de manual do Solaris diz:

 The vfork() and vforkx() functions can normally be used  the
 same  way  as  fork() and forkx(), respectively. The calling
 procedure, however, should not return while running  in  the
 child's  context,  since the eventual return from vfork() or
 vforkx() in the parent would be to a  stack  frame  that  no
 longer  exists. 

Portanto, a implementação pode fazer o que quiser. A implementação Solaris usa memória compartilhada para o quadro de pilha da chamada de função vfork(). Nenhuma implementação concede acesso a partes mais antigas da pilha do pai.

esperto
fonte
4
Nem a biblioteca GNU C nem a biblioteca musl C são implementadas posix_spawn()no Linux vfork(). Ambos o implementam em cima de __clone().
JdeBP # 15/18
1
@JdeBP: Você sabe que vfork()apenas liga, clone()certo? É literalmente uma linha no kernel.
Joshua
1
"Importante: no Linux, não há implementação real de vfork ()." <- Isso não é verdade e não é verdade há pelo menos uma década. Se o benchmark do shell não estiver observando nenhuma diferença de desempenho entre vforke forkno Linux, ele estará fazendo algo errado.
Zwol 15/10
1
A segunda metade desta resposta, iniciada com "Importante: no Linux, não existe uma implementação real do vfork ()", está quase totalmente errada.
R .. GitHub Pare de ajudar o gelo
1
Por favor, não faça reivindicações sem verificar. O Bourne Shell atual pode ser compilado com e sem suporte a vfork; portanto, mesmo que você acredite que os recursos de depuração do Linux não possam fornecer resultados confiáveis, você poderá comparar os tempos de execução de uma chamada de configuração com e com o vfork no shell. Eu uso um script de configuração com 800 testes. No Solaris, o Bourne Shell usando vfork precisa de 30% menos tempo de CPU do sistema no total em comparação com o gabinete do garfo. No Linux, o mesmo teste resulta em menos de 10% menos tempo de CPU do sistema. No Solaris, não é 3x, pois há muitas chamadas do compilador incluídas.
schily 15/10