¿Por qué se pretende usar vfork () cuando el proceso hijo llama a exec () o exit () inmediatamente después de la creación?


11

Los conceptos del sistema operativo y APUE dicen

Con vfork (), el proceso primario se suspende y el proceso secundario usa el espacio de direcciones del primario. Debido a que vfork () no usa copia en escritura, si el proceso secundario cambia alguna página del espacio de direcciones del padre, las páginas alteradas serán visibles para el padre una vez que se reanude. Por lo tanto, vfork () debe usarse con precaución para garantizar que el proceso secundario no modifique el espacio de direcciones del principal.

vfork () está diseñado para usarse cuando el proceso hijo llama a exec () o exit () inmediatamente después de la creación.

¿Cómo entenderé la última oración?

Cuando un proceso hijo creado por vfork()llamadas exec(), ¿no exec()modifica el espacio de direcciones del proceso padre al cargar el nuevo programa?

Cuando un proceso hijo creado por vfork()llamadas exit(), ¿ exit()no modifica el espacio de direcciones del proceso padre al finalizar el hijo?

Tengo preferencia por Linux.

Gracias.

Respuestas:


15

Cuando un proceso hijo creado por vfork()llamadas exec(), ¿no exec()modifica el espacio de direcciones del proceso padre al cargar el nuevo programa?

No, exec()proporciona un nuevo espacio de direcciones para el nuevo programa; no modifica el espacio de dirección principal. Consulte, por ejemplo, la discusión de las execfunciones en POSIX y la página de execve()manual de Linux .

Cuando un proceso hijo creado por vfork () llama a exit (), ¿exit () no modifica el espacio de direcciones del proceso padre al finalizar el hijo?

Plain exit()might: ejecuta ganchos de salida instalados por el programa en ejecución (incluidas sus bibliotecas). vfork()es más restrictivo por lo tanto, en Linux, exige el uso del _exit()cual no llama a las funciones de limpieza de la biblioteca C.

vfork()resultó ser bastante difícil acertar; se ha eliminado en las versiones actuales del estándar POSIX, y posix_spawn()debería usarse en su lugar.

Sin embargo, a menos que realmente sabe lo que está haciendo, usted debe no utilizar cualquiera vfork()o posix_spawn(); atenerse a los viejos fork()y buenos exec().

La página de manual de Linux vinculada anteriormente proporciona más contexto:

Sin embargo, en los viejos tiempos fork(2) se requeriría hacer una copia completa del espacio de datos de la persona que llama, a menudo innecesariamente, ya que generalmente se realiza inmediatamente después exec(3). Por lo tanto, para una mayor eficiencia, BSD introdujo la vfork() llamada al sistema, que no copió completamente el espacio de direcciones del proceso padre, sino que tomó prestada la memoria del padre y el hilo de control hasta que se execve(2)produjo una llamada o una salida. El proceso padre se suspendió mientras el niño usaba sus recursos. El uso de vfork()fue complicado: por ejemplo, no modificar los datos en el proceso padre dependía de saber qué variables se mantenían en un registro.


Gracias. "exec () proporciona un nuevo espacio de direcciones para el nuevo programa;" ¿Es el comportamiento normal de exec () cargar un programa en el espacio de direcciones del proceso? No encontré en los dos enlaces donde crea un nuevo espacio de direcciones, normalmente o particularmente para vfork ().
Tim

1
Lo divertido es que vfork () está ganando contra casi todo lo demás ahora. Es ridículamente más rápido que fork () cuando tienes un gigabyte de memoria grabable.
Joshua

2
Por favor no le digas a la gente que use posix_spawn. Es significativamente más difícil escribir el código correcto usando posix_spawnque con el viejo fork, y si lo intentas, puedes toparte con la pared de ladrillos de que no haya una acción o atributo de archivo que haga lo que necesitas hacer entre forky exec. Y no se garantiza que tenga una eficiencia similar a la de vfork, por lo que ni siquiera resuelve el problema que la gente quiere que resuelva.
zwol

1
@zwol: Eso es realmente un mal consejo. Si bien posix_spawnpuede carecer de la funcionalidad que desea (puede resolver esto a través de un programa auxiliar intermedio, escrito en C o script de shell inline-on-cmdline), cualquier intento de lograr lo que desea con vforkinvoca un comportamiento indefinido peligroso. La especificación para vforkno permite llamar a funciones aleatorias para configurar el estado para que el hijo herede antes execve, y los intentos de hacerlo pueden dañar el estado del padre.
R .. GitHub DEJA DE AYUDAR AL HIELO

1
@Joshua: Una implementación moderna de posix_spawnrealiza aproximadamente lo mismo que vforken la mayoría de las condiciones. Los casos en los que hay una diferencia tienden a ser exactamente los casos en los que vforkes altamente inseguro: donde hay controladores de señales instalados que deben evitar que posix_spawnse ejecuten en el niño antes de la ejecución.
R .. GitHub DEJA DE AYUDAR AL HIELO

4

Cuando llama vfork(), se crea un nuevo proceso y ese nuevo proceso toma prestada la imagen del proceso padre con la excepción de la pila. El proceso hijo recibe una nueva estrella de pila, sin embargo, no permite returndesde la función que llamó vfork().

Mientras el niño se está ejecutando, el proceso padre se bloquea, ya que el niño tomó prestado el espacio de direcciones del padre.

Independientemente de lo que haga, todo lo que solo accede a la pila modifica solo la pila privada del niño. Sin embargo, si modifica los datos globales, esto modifica los datos comunes y, por lo tanto, también afecta al padre.

Las cosas que modifican los datos globales son, por ejemplo:

  • llamando a malloc () o gratis ()

  • usando stdio

  • modificar la configuración de la señal

  • modificando variables que no son locales a la función que llamó vfork().

  • ...

Una vez que llame _exit()(importante, nunca llame exit()), el niño termina y el control se devuelve al padre.

Si llama a cualquier función de la exec*()familia, se crea un nuevo espacio de direcciones con un nuevo código de programa, nuevos datos y una parte de la pila del padre (ver más abajo). Una vez que esto está listo, el niño ya no toma prestado el espacio de direcciones del niño, sino que usa un espacio de direcciones propio.

El control se devuelve al padre, ya que su espacio de direcciones ya no está en uso por otro proceso.

Importante: en Linux, no hay una vfork()implementación real . Linux implementa más bien vfork()basado en el fork()concepto Copy on Write introducido por SunOS-4.0 en 1988. Para hacer que los usuarios crean que usan vfork(), Linux simplemente configura datos compartidos y suspende al padre mientras el niño no llamó _exit()o una de las exec*()funciones.

Por lo tanto, Linux no se beneficia del hecho de que un real vfork()no necesita configurar una descripción de espacio de direcciones para el niño en el núcleo. Esto resulta en un vfork()que no es más rápido que fork(). En los sistemas que implementan un real vfork(), generalmente es 3 veces más rápido fork()y afecta el rendimiento de los shells que usan vfork()- ksh93, el reciente Bourne Shelly csh.

La razón por la que nunca debe llamar exit()desde el elemento vfork()secundario ed es que exit()elimina stdio en caso de que haya datos no reflejados del momento anterior a la llamada vfork(). Esto podría causar resultados extraños.

Por cierto: posix_spawn()se implementa además vfork(), por vfork()lo que no se eliminará del sistema operativo. Se ha mencionado que Linux no usa vfork()para posix_spawn().

Para la pila, hay poca documentación, esto es lo que dice la página de manual de Solaris:

 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. 

Entonces la implementación puede hacer lo que quiera. La implementación de Solaris usa memoria compartida para el marco de la pila de la función que llama vfork(). Ninguna implementación otorga acceso a las partes más antiguas de la pila desde el padre.


44
Ni la biblioteca GNU C ni la biblioteca musl C se implementan posix_spawn()sobre Linux vfork(). Ambos lo implementan encima __clone().
JdeBP

1
@JdeBP: Sabes que vfork()solo llama, clone()¿verdad? Es literalmente una línea en el núcleo.
Joshua

1
"Importante: en Linux, no existe una implementación real de vfork ()". <- Esto no es cierto y no lo ha sido durante al menos una década. Si su punto de referencia de shell no observa ninguna diferencia de rendimiento entre vforky forken Linux, está haciendo algo mal.
zwol

1
La segunda mitad de esta respuesta que comienza con "Importante: en Linux, no hay una implementación real de vfork ()" es en su mayoría o completamente incorrecta.
R .. GitHub DEJA DE AYUDAR AL HIELO

1
Por favor no haga reclamos sin verificar. El Bourne Shell actual se puede compilar con y sin soporte de vfork, por lo que incluso si cree que las funciones de depuración de Linux no pueden proporcionar resultados confiables, puede comparar los tiempos de ejecución de una llamada de configuración con y con vfork en el shell. Yo uso un script de configuración con 800 pruebas. En Solaris, el Bourne Shell que usa vfork necesita un 30% menos de tiempo de CPU del sistema en total en comparación con el caso de la bifurcación. En Linux, la misma prueba da como resultado menos del 10% menos de tiempo de CPU del sistema. En Solaris no es 3x ya que hay muchas llamadas de compilador incluidas.
schily
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.