La diferencia entre fork (), vfork (), exec () y clone ()

197

Estaba buscando encontrar la diferencia entre estos cuatro en Google y esperaba que hubiera una gran cantidad de información sobre esto, pero realmente no hubo una comparación sólida entre las cuatro llamadas.

Me puse a tratar de compilar una especie de vistazo básico de un vistazo a las diferencias entre estas llamadas al sistema y esto es lo que obtuve. ¿Es correcta toda esta información / me falta algo importante?

Fork : La llamada fork básicamente hace un duplicado del proceso actual, idéntico en casi todos los sentidos (no todo se copia, por ejemplo, los límites de recursos en algunas implementaciones, pero la idea es crear una copia lo más cercana posible).

El nuevo proceso (hijo) obtiene una ID de proceso diferente (PID) y tiene el PID del proceso anterior (padre) como su PID padre (PPID). Debido a que los dos procesos ahora están ejecutando exactamente el mismo código, pueden determinar cuál es cuál mediante el código de retorno de fork: el niño obtiene 0, el padre obtiene el PID del niño. Esto es todo, por supuesto, suponiendo que la llamada fork funcione; de ​​lo contrario, no se crea ningún elemento secundario y el padre obtiene un código de error.

Vfork: La diferencia básica entre vfork y fork es que cuando se crea un nuevo proceso con vfork (), el proceso padre se suspende temporalmente y el proceso hijo podría tomar prestado el espacio de direcciones del padre. Este extraño estado de cosas continúa hasta que el proceso secundario sale o llama a execve (), momento en el cual el proceso primario continúa.

Esto significa que el proceso hijo de vfork () debe tener cuidado para evitar modificar inesperadamente las variables del proceso padre. En particular, el proceso secundario no debe regresar de la función que contiene la llamada vfork (), y no debe llamar a exit () (si necesita salir, debe usar _exit (); en realidad, esto también es cierto para el secundario de un tenedor normal ()).

Exec :La llamada ejecutiva es una forma de reemplazar básicamente todo el proceso actual con un nuevo programa. Carga el programa en el espacio de proceso actual y lo ejecuta desde el punto de entrada. exec () reemplaza el proceso actual con el ejecutable señalado por la función. El control nunca vuelve al programa original a menos que haya un error exec ().

Clone :Clone, como fork, crea un nuevo proceso. A diferencia de fork, estas llamadas permiten que el proceso secundario comparta partes de su contexto de ejecución con el proceso de llamada, como el espacio de memoria, la tabla de descriptores de archivos y la tabla de manejadores de señales.

Cuando el proceso hijo se crea con clone, ejecuta la aplicación de función fn (arg). (Esto difiere de fork, donde la ejecución continúa en el hijo desde el punto de la llamada fork original). El argumento fn es un puntero a una función que el proceso hijo invoca al comienzo de su ejecución. El argumento arg se pasa a la función fn.

Cuando la aplicación de la función fn (arg) regresa, el proceso secundario finaliza. El número entero devuelto por fn es el código de salida para el proceso secundario. El proceso secundario también puede finalizar explícitamente llamando a exit (2) o después de recibir una señal fatal.

Información obtenida de forma:

¡Gracias por tomarse el tiempo de leer esto! :)

usuario476033
fuente
2
¿Por qué vfork no debe llamar a exit ()? O no volver? ¿No sale () solo usa _exit ()? También estoy tratando de entender :)
LazerSharks
2
@Gnuey: porque potencialmente (si está implementado de manera diferente a fork()Linux, y probablemente a todos los BSD) toma prestado el espacio de direcciones de su padre. Todo lo que hace, además de llamar execve()o _exit()tiene un gran potencial para confundir a los padres. En particular, los manejadores de exit()llamadas atexit()y otros "finalizadores", p. Ej .: lava los flujos stdio. Regresar de un vfork()niño podría potencialmente (la misma advertencia que antes) ensuciar la pila de los padres.
ninjalj
Me preguntaba qué pasa con los hilos del proceso principal; ¿Están todos clonados o solo el hilo que llama a la llamada al forksistema?
Mohammad Jafar Mashhadi
@LazerSharks vfork produce un proceso similar a un hilo en el que la memoria se comparte sin protecciones de copia contra escritura, por lo que hacer cosas de la pila podría destruir el proceso principal.
Jasen

Respuestas:

159
  • vfork()Es una optimización obsoleta. Antes de una buena gestión de la memoria, fork()hacía una copia completa de la memoria de los padres, por lo que era bastante costosa. dado que en muchos casos fork()se siguió a exec(), que descarta el mapa de memoria actual y crea uno nuevo, fue un gasto innecesario. Hoy en día, fork()no copia la memoria; simplemente se configura como "copiar al escribir", por lo que fork()+ exec()es tan eficiente como vfork()+ exec().

  • clone()es la llamada al sistema utilizada por fork(). con algunos parámetros, crea un nuevo proceso, con otros, crea un hilo. la diferencia entre ellos es qué estructuras de datos (espacio de memoria, estado del procesador, pila, PID, archivos abiertos, etc.) se comparten o no.

Javier
fuente
22
vforkevita la necesidad de almacenar temporalmente mucha más memoria solo para que uno pueda ejecutar exec, y todavía es más eficiente que fork, incluso si no es tan alto. Por lo tanto, se puede evitar tener que comprometer la memoria en exceso para que un programa grande y poderoso pueda generar un proceso secundario. Por lo tanto, no solo aumenta el rendimiento, sino que podría hacerlo factible.
Deduplicador
55
En realidad, he sido testigo de primera mano de cómo fork () está lejos de ser barato cuando tu RSS es grande. Supongo que esto se debe a que el núcleo todavía tiene que copiar todas las tablas de páginas.
Martina Ferrari
44
Tiene que copiar todas las tablas de páginas, configurar todas las copias de escritura en memoria en ambos procesos , vaciar el TLB y luego debe revertir todos los cambios en el padre (y vaciar el TLB nuevamente) exec.
zwol
3
vfork todavía es útil en cygwin (un kernel que emula dll, que se ejecuta sobre Windows de Microsoft). cygwin no puede implementar una bifurcación eficiente, ya que el sistema operativo subyacente no tiene una.
ctrl-alt-delor
80
  • execve() reemplaza la imagen ejecutable actual con otra cargada desde un archivo ejecutable.
  • fork() crea un proceso hijo.
  • vfork()es una versión histórica optimizada fork(), destinada a utilizarse cuando execve()se llama directamente después defork() . Resultó funcionar bien en sistemas que no son MMU (donde fork()no pueden funcionar de manera eficiente) y al fork()procesar procesos con una gran huella de memoria para ejecutar algún programa pequeño (piense en Java Runtime.exec()). POSIX ha estandarizado el posix_spawn()para reemplazar estos dos últimos usos más modernos devfork() .
  • posix_spawn()hace el equivalente de a fork()/execve(), y también permite un poco de malabares fd en el medio. Se supone que debe reemplazarfork()/execve() , principalmente para plataformas que no son MMU.
  • pthread_create() crea un nuevo hilo.
  • clone() es una llamada específica de Linux, que se puede usar para implementar cualquier cosa desde fork() hasta pthread_create(). Da mucho control. Inspirado enrfork() .
  • rfork()es una llamada específica al Plan-9. Se supone que es una llamada genérica, que permite varios grados de intercambio, entre procesos completos y subprocesos.
ninjalj
fuente
2
Gracias por agregar más información de la que realmente se solicitó, me ayudó a ahorrar tiempo
Neeraj
55
El plan 9 es una provocación.
JJ
1
Para aquellos que no pueden recordar lo que significa MMU: "Unidad de gestión de memoria" - lectura adicional en Wikipedia
mgarey
43
  1. fork()- crea un nuevo proceso hijo, que es una copia completa del proceso padre. Los procesos hijo y padre utilizan diferentes espacios de direcciones virtuales, que inicialmente se rellenan con las mismas páginas de memoria. Luego, a medida que se ejecutan ambos procesos, los espacios de direcciones virtuales comienzan a diferir cada vez más, porque el sistema operativo realiza una copia diferida de las páginas de memoria que están siendo escritas por cualquiera de estos dos procesos y asigna copias independientes de las páginas modificadas de memoria para cada proceso. Esta técnica se llama Copiar en escritura (COW).
  2. vfork()- crea un nuevo proceso hijo, que es una copia "rápida" del proceso padre. A diferencia de la llamada al sistema fork(), los procesos hijo y padre comparten el mismo espacio de dirección virtual. ¡NOTA! Usando el mismo espacio de dirección virtual, tanto el padre como el hijo usan la misma pila, el puntero de la pila y el puntero de instrucciones, como en el caso del clásico fork(). Para evitar interferencias no deseadas entre padre e hijo, que usan la misma pila, la ejecución del proceso padre se congela hasta que el niño llame exec()(crear un nuevo espacio de dirección virtual y una transición a una pila diferente) o _exit()(finalización de la ejecución del proceso ) vfork()es la optimización de (incluso teniendo en cuenta COW), la implementación defork() modelo "fork-and-exec". Se puede realizar 4-5 veces más rápido que el fork(), porque a diferencia delfork()vfork() la llamada al sistema no incluye la creación de un nuevo espacio de direcciones (la asignación y la configuración de nuevos directorios de páginas).
  3. clone()- Crea un nuevo proceso hijo. Varios parámetros de esta llamada al sistema, especifican qué partes del proceso principal deben copiarse en el proceso secundario y qué partes se compartirán entre ellos. Como resultado, esta llamada al sistema se puede utilizar para crear todo tipo de entidades de ejecución, comenzando desde subprocesos y terminando mediante procesos completamente independientes. De hecho, la clone()llamada al sistema es la base que se utiliza para la implementación pthread_create()y todas las fork()llamadas de la familia del sistema.
  4. exec()- restablece toda la memoria del proceso, carga y analiza el binario ejecutable especificado, configura una nueva pila y pasa el control al punto de entrada del ejecutable cargado. Esta llamada al sistema nunca devuelve el control a la persona que llama y sirve para cargar un nuevo programa al proceso ya existente. Esta llamada al fork()sistema junto con la llamada del sistema forman un modelo clásico de gestión de procesos UNIX llamado "fork-and-exec".
ZarathustrA
fuente
2
Tenga en cuenta que los requisitos de BSD y POSIX para vforkson tan débiles que sería legal hacer vforkun sinónimo de fork(y POSIX.1-2008 elimina completamente vforkde la especificación). Si prueba su código en un sistema que los sinonice (por ejemplo, la mayoría de los BSD posteriores a 4.4 aparte de NetBSD, kernels Linux anteriores a 2.2.0-pre6, etc.), puede funcionar incluso si viola el vforkcontrato y luego explota si lo ejecutas en otro lado Algunos de los que lo simulan con fork(por ejemplo, OpenBSD) aún garantizan que el padre no reanude la ejecución hasta que el hijo execo hija _exit. Es ridículamente no portátil.
ShadowRanger
2
con respecto a la última oración de su 3er punto: noté en Linux usando strace que, si bien, de hecho, el contenedor glibc para fork () llama al clon syscall, el contenedor para vfork () llama al vfork syscall
ilstam
7

El fork (), vfork () y clone () todos llaman a do_fork () para hacer el trabajo real, pero con diferentes parámetros.

asmlinkage int sys_fork(struct pt_regs regs)
{
    return do_fork(SIGCHLD, regs.esp, &regs, 0);
}

asmlinkage int sys_clone(struct pt_regs regs)
{
    unsigned long clone_flags;
    unsigned long newsp;

    clone_flags = regs.ebx;
    newsp = regs.ecx;
    if (!newsp)
        newsp = regs.esp;
    return do_fork(clone_flags, newsp, &regs, 0);
}
asmlinkage int sys_vfork(struct pt_regs regs)
{
    return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, &regs, 0);
}
#define CLONE_VFORK 0x00004000  /* set if the parent wants the child to wake it up on mm_release */
#define CLONE_VM    0x00000100  /* set if VM shared between processes */

SIGCHLD means the child should send this signal to its father when exit.

Para fork, el niño y el padre tienen la tabla de páginas VM independiente, pero desde la eficiencia, fork realmente no copiará ninguna página, solo configura todas las páginas que se pueden escribir para que sean solo de lectura para el proceso hijo. Entonces, cuando el proceso secundario desea escribir algo en esa página, se produce una excepción de página y el núcleo asignará una nueva página clonada de la página anterior con permiso de escritura. Eso se llama "copiar al escribir".

Para vfork, la memoria virtual es exactamente por hijo y padre --- solo por eso, padre e hijo no pueden estar despiertos al mismo tiempo ya que se influirán mutuamente. Por lo tanto, el padre dormirá al final de "do_fork ()" y se despertará cuando el niño llame a exit () o execve () desde entonces tendrá una nueva tabla de páginas. Aquí está el código (en do_fork ()) que el padre duerme.

if ((clone_flags & CLONE_VFORK) && (retval > 0))
down(&sem);
return retval;

Aquí está el código (en mm_release () llamado por exit () y execve ()) que despierta al padre.

up(tsk->p_opptr->vfork_sem);

Para sys_clone (), es más flexible ya que puede ingresar cualquier clone_flags. Entonces pthread_create () llama a esta llamada al sistema con muchos clone_flags:

int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM);

Resumen: fork (), vfork () y clone () crearán procesos secundarios con un montaje diferente de recursos compartidos con el proceso padre. También podemos decir que vfork () y clone () pueden crear hilos (en realidad son procesos, ya que tienen una estructura de tareas independiente) ya que comparten la tabla de páginas VM con el proceso padre.

usuario991800
fuente
-4

en fork (), el proceso hijo o padre se ejecutará en función de la selección de la CPU. Pero en vfork (), seguramente el hijo se ejecutará primero. después de la terminación del niño, el padre ejecutará.

Raj Kannan B.
fuente
3
Incorrecto. vfork()solo se puede implementar como fork().
ninjalj
después de AnyFork (), no se define quién ejecuta el primer padre / hijo.
AjayKumarBasuthkar
55
@Raj: Tienes algunos malentendidos conceptuales si crees que después de bifurcar hay una noción implícita de orden en serie. La bifurcación crea un nuevo proceso y luego devuelve el control a ambos procesos (cada uno devuelve uno diferente pid): el sistema operativo puede programar el nuevo proceso para que se ejecute en paralelo si tal cosa tiene sentido (por ejemplo, múltiples procesadores). Si por alguna razón necesita que estos procesos se ejecuten en un orden en serie particular, entonces necesita una sincronización adicional que la bifurcación no proporciona; francamente, probablemente ni siquiera quieras un tenedor en primer lugar.
Andon M. Coleman
En realidad, @AjayKumarBasuthkar y @ninjalj, ambos están equivocados. con vfork(), el niño corre primero. Está en las páginas del manual; la ejecución de los padres se suspende hasta que el niño muera o fallezca exec. Y ninjalj busca el código fuente del núcleo. No hay manera de implementar vfork()como fork()porque pasan diferentes argumentos para do_fork()dentro del núcleo. Sin embargo, puede implementar vforkcon la clonellamada al sistema
Zac Wimer
@ZacWimer: véase el comentario de ShadowRanger a otra respuesta stackoverflow.com/questions/4856255/... Antiguo Linux hizo synonimize ellos, ya que aparentemente distintos BSD NetBSD (que tiende a ser portado a una gran cantidad de sistemas no MMU) hacer. Desde la página de manual de Linux: en 4.4BSD se hizo sinónimo de fork (2) pero NetBSD lo presentó nuevamente; ver ⟨netbsd.org / Documentation / kernel / vfork.html⟩. En Linux, ha sido equivalente a fork (2) hasta 2.2.0-pre6 más o menos.
ninjalj