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

10

Os conceitos do sistema operacional e o APUE dizem

With vfork(), the parent process is suspended, and the child process uses the address space of the parent. Because vfork() does not use copy-on-write, if the child process changes any pages of the parent’s address space, the altered pages will be visible to the parent once it resumes. Therefore, vfork() must be used with caution to ensure that the child process does not modify the address space of the parent.

vfork() is intended to be used when the child process calls exec() or exit() immediately after creation.

Como devo entender a última frase?

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

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

Eu tenho preferência pelo Linux.

Obrigado.

    
por Tim 15.10.2018 / 18:16

2 respostas

15

When a child process created by vfork() calls exec(), doesn't exec() modify the address space of the parent process, by loading the new program?

Não, exec() fornece um novo espaço de endereçamento para o novo programa; não modifica o espaço de endereço pai. Veja, por exemplo, a discussão das funções exec em POSIX e o Linux execve() manpage .

When a child process created by vfork() calls exit(), does exit() not modify the address space of the parent process when terminating the child?

Plain exit() might - ele executa os ganchos de saída instalados pelo programa em execução (incluindo suas bibliotecas). vfork() é mais restritivo; assim, no Linux, manda o uso de _exit() que não chama as funções de limpeza da biblioteca C.

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

No entanto, a menos que você realmente saiba o que está fazendo, não use vfork() ou posix_spawn() ; manter o bom e velho fork() e exec() .

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

However, in the bad old days a fork(2) would require making a complete copy of the caller's data space, often needlessly, since usually immediately afterward an exec(3) is done. Thus, for greater efficiency, BSD introduced the vfork() system call, which did not fully copy the address space of the parent process, but borrowed the parent's memory and thread of control until a call to execve(2) or an exit occurred. The parent process was suspended while the child was using its resources. The use of vfork() was tricky: for example, not modifying data in the parent process depended on knowing which variables were held in a register.

    
por 15.10.2018 / 18:25
4

Quando você chama vfork() , um novo processo é criado e esse novo processo usa a imagem do processo pai com exceção da pilha. O processo filho recebe uma nova estrela de pilha própria, mas não permite return da função que chamou vfork() .

Enquanto o filho está em execução, o processo pai é bloqueado, pois o filho pediu emprestado o espaço de endereço do pai.

Independentemente do que você faz, tudo o que apenas acessa a pilha modifica somente a pilha privada do filho. Se, no entanto, você modificar dados globais, isso modifica os dados comuns e, portanto, também afeta o pai.

As coisas que modificam dados globais são, por exemplo:

  • chamando malloc () ou free ()

  • usando stdio

  • modificando as configurações do sinal

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

  • ...

Uma vez que você chamar _exit() (importante, nunca chame exit() ), o filho é terminado e o controle é devolvido ao pai.

Se você chamar qualquer função da família exec*() , 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). Quando isso estiver pronto, a criança não mais toma emprestado o espaço de endereço do filho, mas usa um espaço de endereço próprio.

O controle é devolvido ao pai, já que o espaço de endereço não está mais em uso por outro processo.

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

O Linux, portanto, não se beneficia do fato de que um vfork() real não precisa configurar uma descrição de 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% realvfork(), ele é normalmente 3x mais rápido que fork() e afeta o desempenho de shells que usam vfork() - ksh93 , o recente Bourne Shell e csh .

O motivo pelo qual você nunca deve chamar exit() do filho vfork() ed é que exit() libera o stdio caso haja dados não-liberados do tempo antes de chamar 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 man 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. 

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

    
por 15.10.2018 / 18:52