En informática , `fork` es una operación mediante la cual un proceso crea una copia de sí mismo. Generalmente se implementa como una envoltura de la biblioteca estándar de C para las llamadas al sistema `fork`, `clone` u otras llamadas del kernel . Durante muchos años, `fork` fue el método principal de creación de procesos en sistemas operativos Unix y similares , y sigue siendo una interfaz necesaria para cumplir con POSIX . A pesar de esto, ha caído en desuso en los últimos años debido a fallas como un rendimiento deficiente, falta de seguridad para subprocesos y su condición de fuente común de vulnerabilidades de seguridad . [ 1 ]
Descripción general
En los sistemas operativos multitarea , los procesos (programas en ejecución) necesitan una forma de crear nuevos procesos, por ejemplo, para ejecutar otros programas. El método `fork` y sus variantes suelen ser la única forma de hacerlo en sistemas tipo Unix. Para que un proceso inicie la ejecución de un programa diferente, primero crea una copia de sí mismo mediante `fork`. Luego, la copia, llamada " proceso hijo ", realiza los cambios de entorno necesarios y llama a la llamada al sistema `exec` para superponerse con el nuevo programa: detiene la ejecución de su programa anterior en favor del nuevo. (O, en casos más raros, el proceso hijo omite ` exec` y continúa ejecutando, como un proceso independiente, alguna otra funcionalidad del programa original).
La operación fork crea un espacio de direcciones separado para el proceso hijo. El proceso hijo tiene una copia exacta de todos los segmentos de memoria del proceso padre. En las variantes modernas de UNIX que siguen el modelo de memoria virtual de SunOS 4.0, se implementa la semántica de copia en escritura y no es necesario copiar la memoria física. En cambio, las páginas de memoria virtual en ambos procesos pueden hacer referencia a las mismas páginas de memoria física hasta que uno de ellos escribe en dicha página: entonces se copia. Esta optimización es importante en el caso común en que fork se usa junto con exec para ejecutar un nuevo programa: normalmente, el proceso hijo realiza solo un pequeño conjunto de acciones antes de detener la ejecución de su programa a favor del programa que se va a iniciar, y requiere muy pocas, o ninguna, de las estructuras de datos de su padre .
Cuando un proceso llama a `fork`, se le considera el proceso padre y el proceso recién creado es su hijo. Tras la llamada a `fork`, ambos procesos no solo ejecutan el mismo programa, sino que reanudan su ejecución como si ambos hubieran llamado a la función del sistema. A continuación, pueden examinar el valor de retorno de la llamada para determinar su estado (padre o hijo) y actuar en consecuencia.
La historia de fork se remonta a la década de 1960, ya que una de las primeras referencias al concepto de fork apareció en A Multiprocessor System Design de Melvin Conway , publicado en 1962. [ 2 ] El artículo de Conway motivó la implementación de fork por L. Peter Deutsch en el sistema de tiempo compartido GENIE , donde Ken Thompson tomó prestado el concepto para su primera aparición [ 3 ] en Research Unix . [ 4 ] [ 5 ] Posteriormente, fork se convirtió en una interfaz estándar en POSIX . [ 6 ]
Técnico
Ejemplo
La siguiente variante del programa "¡Hola, mundo!" demuestra el funcionamiento de la llamada al sistema `fork` en el lenguaje de programación C. El programa se divide en dos procesos, cada uno de los cuales decide qué funcionalidad ejecutar en función del valor de retorno de la llamada al sistema `fork`. Se ha omitido el código repetitivo, como las inclusiones de cabecera .
#include <stdio.h> #include <stdlib.h> #include <unistd.h>int main ( void ) { pid_t pid = fork ();if ( pid == -1 ) { perror ( "fork failed" ); return EXIT_FAILURE ; } else if ( pid == 0 ) { printf ( "¡Hola desde el proceso hijo! \n " ); return EXIT_SUCCESS ; } else { int status ; waitpid ( pid , & status , 0 ); } return EXIT_SUCCESS ; }A continuación se presenta un análisis detallado de este programa.
pid_t pid = fork ();La primera instrucción de main llama a la función del sistema fork para dividir la ejecución en dos procesos. El valor de retorno de fork se registra en una variable de tipo pid_t , que es el tipo POSIX para los identificadores de proceso (PID).
if ( pid == -1 ) { perror ( "fork failed" ); return EXIT_FAILURE ; }El signo menos uno indica un error en fork : no se creó ningún proceso nuevo, por lo que se imprime un mensaje de error.
Si la operación fork se realiza correctamente, entonces existen dos procesos, ambos ejecutando la función principal desde el punto donde fork finalizó. Para que los procesos realicen tareas diferentes, el programa debe bifurcarse según el valor de retorno de fork para determinar si se ejecuta como el proceso hijo o el proceso padre .
else if ( pid == 0 ) { printf ( "¡Hola desde el proceso hijo! \n " ); return EXIT_SUCCESS ; }En el proceso hijo, el valor de retorno es cero (lo que indica un identificador de proceso no válido ). El proceso hijo imprime el mensaje de saludo deseado y finaliza. (Por razones técnicas, se debe usar la función _exit de POSIX en lugar de la función exit estándar de C ).
else { int status ; waitpid ( pid , & status , 0 ); }El otro proceso, el padre, recibe de `fork` el identificador del proceso hijo, que siempre es un número positivo. El proceso padre pasa este identificador a la llamada al sistema `waitpid` para suspender la ejecución hasta que el hijo finalice. Cuando esto ocurre, el padre reanuda la ejecución y finaliza mediante la instrucción `return` .
Comunicación
El proceso hijo comienza con una copia de los descriptores de archivo de su padre . [ 6 ] Para la comunicación entre procesos con intercambio de datos y sincronización entre procesos padre e hijo, se pueden usar tuberías . [ 7 ] El proceso padre a menudo creará una o varias tuberías, y luego, después de la bifurcación, los procesos cerrarán los extremos de las tuberías que no necesiten. [ 7 ] Para compartir la misma variable entre los procesos padre e hijo, se debe usar el mapeo de memoria o la memoria compartida . [ 8 ]
Variantes
Horquilla en V
Vfork es una variante de fork con la misma convención de llamada y una semántica muy similar, pero solo debe usarse en situaciones restringidas. Se originó en la versión 3BSD de Unix, [ 9 ] [ 10 ] [ 11 ] la primera Unix en admitir memoria virtual. Fue estandarizada por POSIX, lo que permitió que vfork tuviera exactamente el mismo comportamiento que fork, pero fue marcada como obsoleta en la edición de 2004 [ 12 ] y fue reemplazada por posix_spawn () (que normalmente se implementa a través de vfork) en ediciones posteriores.
Cuando se emite una llamada al sistema vfork, el proceso padre se suspenderá hasta que el proceso hijo haya completado su ejecución o haya sido reemplazado con una nueva imagen ejecutable a través de una de las llamadas al sistema de la familia " exec ". El hijo toma prestada la configuración de la unidad de administración de memoria del padre y las páginas de memoria se comparten entre el proceso padre e hijo sin realizar copias, y en particular sin semántica de copia en escritura ; [ 12 ] por lo tanto, si el proceso hijo realiza una modificación en cualquiera de las páginas compartidas, no se creará una nueva página y las páginas modificadas también serán visibles para el proceso padre. Dado que no hay absolutamente ninguna copia de página involucrada (consumiendo memoria adicional), esta técnica es una optimización sobre el fork simple en entornos de copia completa cuando se usa con exec. En POSIX, usar vfork para cualquier propósito que no sea como preludio a una llamada inmediata a una función de la familia exec (y algunas otras operaciones selectas) da lugar a un comportamiento indefinido . [ 12 ] Al igual que con vfork, el hijo toma prestadas estructuras de datos en lugar de copiarlas. vfork sigue siendo más rápido que un fork que utiliza semántica de copia en escritura.
El sistema V no admitía esta llamada a función antes de la introducción del sistema VR4, porque el uso compartido de memoria que provoca es propenso a errores:
Vfork no copia las tablas de páginas, por lo que es más rápido que la implementación de fork de System V. Sin embargo, el proceso hijo se ejecuta en el mismo espacio de direcciones físicas que el proceso padre (hasta que se ejecuta exec o exit ) y, por lo tanto, puede sobrescribir los datos y la pila del padre. Podría surgir una situación peligrosa si un programador usa vfork incorrectamente, por lo que la responsabilidad de llamar a vfork recae en el programador. La diferencia entre el enfoque de System V y el de BSD es filosófica: ¿Debería el kernel ocultar las peculiaridades de su implementación a los usuarios, o debería permitir a los usuarios avanzados la oportunidad de aprovechar la implementación para realizar una función lógica de manera más eficiente?
— Maurice J. Bach [ 13 ]
De manera similar, la página man de Linux para vfork desaconseja encarecidamente su uso: [ 9 ]
Resulta bastante lamentable que Linux haya revivido este fantasma del pasado. La página man de BSD indica: "Esta llamada al sistema se eliminará cuando se implementen mecanismos adecuados de compartición de memoria. Los usuarios no deben depender de la semántica de compartición de memoria de vfork(), ya que, en ese caso, se convertirá en sinónimo de fork(2)".
Otros problemas con vfork incluyen interbloqueos que podrían ocurrir en programas multihilo debido a interacciones con el enlace dinámico . [ 14 ] Como reemplazo de la interfaz vfork , POSIX introdujo la familia de funciones posix_spawn que combinan las acciones de fork y exec. Estas funciones pueden implementarse como rutinas de biblioteca en términos de fork , como se hace en Linux, [ 14 ] o en términos de vfork para un mejor rendimiento, como se hace en Solaris, [ 14 ] [ 15 ] pero la especificación POSIX señala que fueron "diseñadas como operaciones del kernel ", especialmente para sistemas operativos que se ejecutan en hardware restringido y sistemas en tiempo real . [ 16 ]
Si bien la implementación de 4.4BSD eliminó la implementación de vfork, lo que provocó que vfork tuviera el mismo comportamiento que fork, posteriormente se reinstauró en el sistema operativo NetBSD por razones de rendimiento. [ 10 ]
Algunos sistemas operativos embebidos, como uClinux, omiten la función fork y solo implementan vfork, porque necesitan operar en dispositivos donde la copia en escritura es imposible de implementar debido a la falta de una unidad de gestión de memoria.
Horquilla R
El sistema operativo Plan 9 , creado por los diseñadores de Unix, incluye fork como una variante de una nueva función llamada "rfork" que permite compartir recursos de forma granular entre procesos padre e hijo, incluyendo el espacio de direcciones (excepto un segmento de pila , que es único para cada proceso), variables de entorno y el espacio de nombres del sistema de archivos; [ 17 ] esto lo convierte en una interfaz unificada para la creación tanto de procesos como de hilos dentro de ellos. [ 18 ] Tanto FreeBSD [ 19 ] como IRIX adoptaron la llamada al sistema rfork de Plan 9, este último renombrándola como "sproc". [ 20 ]
Clon
clonees una llamada al sistema en el kernel de Linux que crea un proceso hijo que puede compartir partes de su contexto de ejecución con el padre. Al igual que rfork de FreeBSD y sproc de IRIX, clone de Linux se inspiró en rfork de Plan 9 y puede usarse para implementar hilos (aunque los programadores de aplicaciones normalmente usarán una interfaz de nivel superior como pthreads , implementada sobre clone). La característica de "pilas separadas" de Plan 9 e IRIX se ha omitido porque (según Linus Torvalds ) causa demasiada sobrecarga. [ 20 ]
La bifurcación ocurre en otros sistemas operativos.
En el diseño original del sistema operativo VMS (1977), una operación de copia con posterior modificación del contenido de algunas direcciones específicas para el nuevo proceso, como en la bifurcación, se consideraba arriesgada. Los errores en el estado del proceso actual podían copiarse a un proceso hijo. Aquí se utiliza la metáfora de la generación de procesos: cada componente de la distribución de memoria del nuevo proceso se construye desde cero. Esta metáfora fue adoptada posteriormente por los sistemas operativos de Microsoft (1993).
El componente de compatibilidad POSIX de VM/CMS (OpenExtensions) proporciona una implementación muy limitada de fork, en la que el proceso padre se suspende mientras se ejecuta el hijo, y ambos comparten el mismo espacio de direcciones. [ 21 ] Esto es esencialmente un vfork etiquetado como fork . (Esto se aplica solo al sistema operativo invitado CMS; otros sistemas operativos invitados de VM, como Linux, proporcionan la funcionalidad fork estándar).
Véase también
Referencias
- ↑ Baumann, Andrew; Appavoo, Jonathan; Krieger, Orran; Roscoe, Timothy (13-15 de mayo de 2019). Una bifurcación en el camino . Taller sobre temas candentes en sistemas operativos (HotOS '19). Bertinoro, Italia: ACM. doi : 10.1145/3317550.3321435 .
- ↑ Nyman, Linus (25 de agosto de 2016). "Notas sobre la historia de Fork y Join". IEEE Annals of the History of Computing . 38 (3): 84– 87. doi : 10.1109/MAHC.2016.34 .
- ↑ "s3.s de Research UNIX" . GitHub . 1970.
- ↑ Ken Thompson y Dennis Ritchie (3 de noviembre de 1971). "SYS FORK (II)" (PDF) . Manual del programador de UNIX . Bell Laboratories .
- ↑ Ritchie, Dennis M. ; Thompson, Ken (julio de 1978). "El sistema de tiempo compartido UNIX" (PDF) . Bell System Tech. J. 57 ( 6). AT&T: 1905–1929 . doi : 10.1002/j.1538-7305.1978.tb02136.x . Recuperado el 22 de abril de 2014 .
- 1 2 – Referencia de interfaces del sistema, Especificación única de UNIX , Versión 5 de The Open Group
- 1 2 – Referencia de interfaces del sistema, Especificación única de UNIX , Versión 5 de The Open Group
- ↑ "shmat()" . Página man de Linux . Consultado el 12 de julio de 2025 .
- 1 2 – Manual del programador de Linux – Llamadas al sistema de Manned.org
- 1 2 "Documentación de NetBSD: ¿Por qué implementar vfork() tradicional?" . Proyecto NetBSD . Consultado el 16 de octubre de 2013 .
- ↑ "vfork(2)". Manual del programador de UNIX, versión virtual VAX-11 . Universidad de California, Berkeley. Diciembre de 1979.
- 1 2 3 – Referencia de interfaces del sistema, Especificación única de UNIX , Versión 3 de The Open Group
- ↑ Bach, Maurice J. (1986). El diseño del sistema operativo UNIX . Prentice–Hall. págs. 291–292 . Bibcode : 1986duos.book.....B .
- 1 2 3 Nakhimovsky, Greg (mayo de 2006). "Minimización del uso de memoria para la creación de subprocesos de aplicaciones" . Oracle Technology Network . Oracle Corporation . Archivado del original el 22 de septiembre de 2019.
- ↑ Implementación de posix_spawn() en OpenSolaris
- ↑ – Referencia de interfaces del sistema, Especificación única de UNIX , Versión 5 de The Open Group
- ↑ – Manual del programador de Plan 9 , Volumen 1
- ↑ – Manual del programador de Plan 9 , Volumen 1
- ↑ – Manual de llamadas al sistema de FreeBSD
- 1 2 Torvalds, Linus (1999). "The Linux edge" . Open Sources: Voices from the Open Source Revolution . O'Reilly. ISBN 978-1-56592-582-3.
- ↑ "z/VM > z/VM 6.2.0 > Programación de aplicaciones > Documento de conformidad POSIX de OpenExtensions de z/VM V6R2 > Documento de conformidad POSIX.1 > Sección 3. Primitivas de proceso > 3.1 Creación y ejecución de procesos > 3.1.1 Creación de procesos" . IBM . Consultado el 21 de abril de 2015 .
- Proceso (informática)
- Biblioteca C POSIX
- llamadas al sistema