¿Por qué necesitamos bifurcar para crear nuevos procesos?


95

En Unix cada vez que queremos crear un nuevo proceso, bifurcamos el proceso actual, creando un nuevo proceso hijo que es exactamente el mismo que el proceso padre; luego hacemos una llamada al sistema ejecutivo para reemplazar todos los datos del proceso padre con los del proceso nuevo.

¿Por qué creamos una copia del proceso padre en primer lugar y no creamos un nuevo proceso directamente?


Respuestas:


61

La respuesta corta es, forkestá en Unix porque era fácil de adaptar al sistema existente en ese momento, y porque un sistema predecesor en Berkeley había utilizado el concepto de horquillas.

De La evolución del sistema de tiempo compartido Unix (se ha resaltado el texto relevante ):

El control de procesos en su forma moderna fue diseñado e implementado en un par de días. Es sorprendente lo fácil que se ajusta al sistema existente; Al mismo tiempo, es fácil ver cómo algunas de las características ligeramente inusuales del diseño están presentes precisamente porque representaron cambios pequeños y fáciles de codificar en lo que existía . Un buen ejemplo es la separación de las funciones fork y exec. El modelo más común para la creación de nuevos procesos implica especificar un programa para que el proceso se ejecute; en Unix, un proceso bifurcado continúa ejecutando el mismo programa que su padre hasta que realiza un exec explícito. La separación de las funciones ciertamente no es exclusiva de Unix, y de hecho estuvo presente en el sistema de tiempo compartido de Berkeley, que Thompson conocía bien. Aún así, parece razonable suponer que existe en Unix principalmente debido a la facilidad con la que se podría implementar fork sin cambiar mucho más . El sistema ya manejó múltiples (es decir, dos) procesos; había una tabla de procesos, y los procesos se intercambiaron entre la memoria principal y el disco. La implementación inicial de fork solo requiere

1) Expansión de la tabla de procesos

2) Se agregó una llamada fork que copió el proceso actual en el área de intercambio de discos, utilizando las primitivas de IO de intercambio ya existentes, y realizó algunos ajustes en la tabla de procesos.

De hecho, la llamada de la bifurcación del PDP-7 requirió exactamente 27 líneas de código de ensamblaje. Por supuesto, se requirieron otros cambios en el sistema operativo y los programas de usuario, y algunos de ellos fueron bastante interesantes e inesperados. Pero un tenedor-ejecutivo combinado habría sido considerablemente más complicado , aunque solo fuera porque el ejecutivo como tal no existía; su función ya fue realizada, usando IO explícito, por el shell.

Desde ese documento, Unix ha evolucionado. forkseguido execya no es la única forma de ejecutar un programa.

  • vfork fue creado para ser una bifurcación más eficiente para el caso en el que el nuevo proceso intenta hacer un ejecutivo justo después de la bifurcación. Después de hacer un vfork, los procesos padre e hijo comparten el mismo espacio de datos, y el proceso padre se suspende hasta que el proceso hijo ejecute un programa o salga.

  • posix_spawn crea un nuevo proceso y ejecuta un archivo en una sola llamada al sistema. Toma un montón de parámetros que le permiten compartir selectivamente los archivos abiertos de la persona que llama y copiar su disposición de señal y otros atributos al nuevo proceso.


55
Buena respuesta, pero agregaría que vfork ya no debería usarse. La diferencia de rendimiento es marginal ahora y su uso puede ser peligroso. Vea esta pregunta SO stackoverflow.com/questions/4856255/…, este sitio ewontfix.com/7 y la "Programación avanzada de Unix" en la página 299 sobre vfork
Raphael Ahrens

44
Las maquinaciones (configuración de la estructura de datos) requeridas para posix_spawn()hacer los mismos trabajos de reacondicionamiento posteriores a la bifurcación que se pueden hacer fácilmente usando un fork()código en línea hacen un argumento convincente para fork()ser mucho más simple de usar.
Jonathan Leffler

34

[Repetiré parte de mi respuesta desde aquí .]

¿Por qué no simplemente tener un comando que crea un nuevo proceso desde cero? ¿No es absurdo e ineficiente copiar uno que solo será reemplazado de inmediato?

De hecho, eso probablemente no sería tan eficiente por algunas razones:

  1. La "copia", producido por fork()un poco de una abstracción, ya que el núcleo utiliza una copia en escritura del sistema ; todo lo que realmente tiene que crearse es un mapa de memoria virtual. Si la copia llama inmediatamente exec(), la mayoría de los datos que se habrían copiado si hubiera sido modificada por la actividad del proceso en realidad nunca tienen que copiarse / crearse porque el proceso no hace nada que requiera su uso.

  2. Varios aspectos importantes del proceso secundario (p. Ej., Su entorno) no tienen que duplicarse o establecerse individualmente en función de un análisis complejo del contexto, etc. Simplemente se supone que son los mismos que los del proceso de llamada, y Este es el sistema bastante intuitivo con el que estamos familiarizados.

Para explicar el # 1 un poco más, la memoria que se "copia" pero nunca se accede posteriormente nunca se copia realmente, al menos en la mayoría de los casos. Una excepción en este contexto podría ser si bifurcas un proceso y luego haces que el proceso principal salga antes de que el secundario se reemplace por sí mismo exec(). Digo podría porque gran parte del padre podría almacenarse en caché si hay suficiente memoria libre, y no estoy seguro de hasta qué punto esto se explotaría (lo que dependería de la implementación del sistema operativo).

Por supuesto, eso no hace que el uso de una copia sea más eficiente que el uso de una pizarra en blanco, excepto que "la pizarra en blanco" no es literalmente nada y debe implicar la asignación. El sistema podría tener una plantilla de proceso genérica en blanco / nueva que copia de la misma manera, 1 pero que en realidad no ahorraría nada frente a la bifurcación de copia en escritura. De modo que el n. ° 1 simplemente demuestra que usar un "nuevo" proceso vacío no sería más eficiente.

El punto n. ° 2 explica por qué el uso de la bifurcación es probablemente más eficiente. El entorno de un niño se hereda de su padre, incluso si es un ejecutable completamente diferente. Por ejemplo, si el proceso principal es un shell y el secundario un navegador web, $HOMEsigue siendo el mismo para ambos, pero dado que cualquiera de ellos podría cambiarlo posteriormente, deben ser dos copias separadas. El del niño es producido por el original fork().

1. Una estrategia que puede no tener mucho sentido literal, pero mi punto es que crear un proceso implica más que copiar su imagen en la memoria del disco.


3
Si bien ambos puntos son verdaderos, ninguno admite por qué se eligió el método de bifurcación en lugar de repetir un nuevo proceso a partir de un ejecutable dado.
SkyDan

3
Creo que esto responde la pregunta. Fork se usa porque, en los casos en que crear un nuevo proceso es la forma más eficiente de hacerlo, el costo de usar fork es trivial (probablemente menos del 1% del costo de creación del proceso). Por otro lado, hay muchos lugares donde fork es dramáticamente más eficiente o mucho más simple que una API (como el manejo de identificadores de archivos). La decisión que tomó Unix fue admitir solo una API, simplificando la especificación.
Cort Ammon

1
@SkyDan Tienes razón, es más una respuesta a por qué no que a por qué , lo que Mark Plotnick responde más directamente, lo que interpretaría que significa no solo que esta fue la opción más fácil, sino también que probablemente fue la más eficiente. elección (según la cita de Dennis Richie: "la llamada de la bifurcación del PDP-7 requirió exactamente 27 líneas de ensamblaje ... exec como tal no existía; su función ya se realizó"). Por lo que este "por qué no" es en realidad una meditando unos dos estrategias en donde uno superficialmente parece más simple y más eficiente, cuando tal vez no lo es (testigo del destino de dudosa ...
goldilocks

1
Ricitos de oro es correcto. Hay situaciones en las que bifurcar y modificar es más barato que crear uno nuevo desde cero. El ejemplo más extremo, por supuesto, es cada vez que desee un comportamiento de tenedor en sí. fork()puede hacerlo muy rápidamente (como GL mencionó, del orden de 27 líneas de ensamblaje). Si busca un "crear un proceso desde cero", mirando en la otra dirección, fork()solo cuesta un poquito más que comenzar desde un proceso creado en blanco (27 líneas de ensamblaje + costo de cierre de identificadores de archivo). Entonces forkmaneja tanto la bifurcación como la creación correcta, mientras createque solo puede manejar la creación correcta.
Cort Ammon

2
Su respuesta se refería a las mejoras de hardware: memoria virtual, copia en escritura. Antes de esto, forkcopiaba toda la memoria del proceso y era muy costoso.
Barmar

6

Creo que la razón por la que Unix solo tenía la forkfunción de crear nuevos procesos es el resultado de la filosofía de Unix

Construyen una función que hace una cosa bien. Crea un proceso hijo.

Lo que se hace con el nuevo proceso depende del programador. Puede usar una de las exec*funciones e iniciar un programa diferente, o no puede usar exec y usar las dos instancias del mismo programa, lo que puede ser útil.

Entonces obtienes un mayor grado de libertad ya que puedes usar

  1. horquilla sin ejecutivo *
  2. horquilla con exec * o
  3. solo ejecutivo * sin tenedor

y, además, sólo tiene que memorizar los forky las exec*llamadas de función, que en la década de 1970 que había que hacer.


3
Entiendo cómo funcionan los tenedores y cómo usarlos. Pero, ¿por qué querría crear un nuevo proceso, cuando puedo hacer lo mismo pero con menos esfuerzo? Por ejemplo, mi maestro me asignó una tarea en la que tenía que crear un proceso para cada número pasado a argv, para verificar si el número es primo. Pero, ¿no es eso solo un desvío de hacer lo mismo en última instancia? Podría haber usado una matriz y una función para cada número ... Entonces, ¿por qué creamos procesos secundarios, en lugar de hacer todo el procesamiento en el proceso principal?
user1534664

2
Me aventuraría a decir que entiendes cómo funcionan los tenedores y cómo usarlos, porque una vez tuviste un maestro que te asignó una tarea en la que tenías que crear un montón de procesos (con el número especificado en el tiempo de ejecución), controlarlos, coordinarlos y comunicarse entre ellos. Por supuesto, nadie haría algo tan trivial como eso en la vida real. Pero, si tiene un gran problema que se descompone fácilmente en piezas que se pueden manejar en paralelo (por ejemplo, detección de bordes en una imagen), la bifurcación le permite usar múltiples núcleos de CPU simultáneamente.
Scott

5

Hay dos filosofías de creación de procesos: bifurcación con herencia y creación con argumentos. Unix usa fork, obviamente. (OSE, por ejemplo, y VMS usan el método create). Unix tiene MUCHAS características heredables, y se agregan más periódicamente. ¡Por herencia, estas nuevas características se pueden agregar SIN CAMBIAR LOS PROGRAMAS EXISTENTES! Al usar un modelo de crear con argumentos, agregar nuevas características significaría agregar nuevos argumentos a la llamada a crear. El modelo Unix es más simple.

También ofrece el muy útil modelo fork-exe-exec, donde un proceso puede dividirse en múltiples partes. Esto fue vital cuando no había forma de E / S asíncrona, y es útil cuando se aprovechan múltiples CPU en un sistema. (Preprocesos). Lo he hecho mucho a lo largo de los años, incluso recientemente. En esencia, permite agrupar múltiples 'programas' en un solo programa, por lo que no hay absolutamente ningún espacio para la corrupción o desajustes de versión, etc.

El modelo fork / exec también ofrece la posibilidad de que un niño específico herede un entorno radicalmente extraño, configurado entre el fork y el ejecutivo. Cosas como descriptores de archivos heredados, especialmente. (Una extensión de stdio fd's). El modelo de creación no ofrece la capacidad de heredar nada que no fue imaginado por los creadores de la llamada de creación.

Algunos sistemas también pueden admitir la compilación dinámica de código nativo, donde el proceso está en efecto escribiendo su propio programa de código nativo. En otras palabras, quiere un nuevo programa que se esté escribiendo sobre la marcha, SIN tener que pasar por el ciclo de código fuente / compilador / enlazador y ocupar espacio en disco. (Creo que hay un sistema de lenguaje Verilog que hace esto). El modelo fork lo admite, el modelo create normalmente no lo haría.


Los descriptores de archivo no son "una extensión de stdio"; Los punteros de archivo estándar son una envoltura alrededor de los descriptores de archivo. Los descriptores de archivos llegaron primero, y son los manejadores fundamentales de E / S de Unix. Pero, de lo contrario, este es un buen punto.
Scott

2

La función fork () no es solo para copiar el proceso padre, devuelve un valor que hace referencia a que el proceso es el proceso padre o hijo, la imagen a continuación explica cómo puede utilizar fork () como padre y hijo:

ingrese la descripción de la imagen aquí

como se muestra cuando el proceso es el padre fork () devuelve el ID del proceso hijo, de lo PID contrario, devuelve0

por ejemplo, puede usarlo si tiene un proceso (servidor web) que recibe las solicitudes y en cada solicitud crea un son processpara procesar esta solicitud, aquí el padre y sus hijos tienen diferentes trabajos.

Por lo tanto, no ejecutar una copia de un proceso no es exactamente lo que fork ().


55
Si bien es cierto, esto no responde la pregunta. ¿Por qué es necesaria la bifurcación para la creación de procesos, si quiero ejecutar un ejecutable diferente?
SkyDan

1
Estoy de acuerdo con SkyDan, esto no responde la pregunta. posix_spawn es una versión algo más elegante de lo que podría haberse imaginado hace 30 años (antes de que Posix existiera) como una función fork_execve ; uno que crea un nuevo proceso, inicializando su imagen desde un ejecutable, sin siquiera insinuar que copia la imagen del proceso padre (excepto la lista de argumentos, el entorno y los atributos del proceso (por ejemplo, el directorio de trabajo)), y devuelve el PID del nuevo proceso a la persona que llama (el proceso padre) .
Scott

1
Hay otras formas de pasar información "principal" a un hijo. La técnica del valor de retorno resulta ser la forma más eficiente de hacerlo desde un principio fork si asumes que quieresfork
Cort Ammon

0

La redirección de E / S se implementa más fácilmente después de fork y antes de exec. El niño, sabiendo que es el niño, puede cerrar los descriptores de archivo, abrir nuevos, dup () o dup2 () para obtener el número fd correcto, etc., todo sin afectar al padre. Después de hacer eso, y tal vez cualquier cambio de variable de entorno deseado (que tampoco afecte al padre), puede ejecutar el nuevo programa en el entorno personalizado.


Todo lo que está haciendo aquí es repetir el tercer párrafo de la respuesta de Jim Cathey con un poquito más de detalle.
Scott

-2

Creo que todos aquí saben cómo funciona fork, pero la pregunta es ¿por qué necesitamos crear un duplicado exacto del padre usando fork? Respuesta ==> Tome un ejemplo de servidor (sin bifurcación), mientras el cliente-1 está accediendo al servidor, si al mismo tiempo llega el segundo cliente-2 y quiere acceder al servidor pero el servidor no da permiso al recién llegado cliente-2 porque el servidor está ocupado para servir al cliente-1 por lo que el cliente-2 tiene que esperar. Después de que todos los servicios al cliente-1 hayan terminado, el cliente-2 ahora puede acceder al servidor. Ahora considere si al mismo tiempo llega el cliente-3, por lo que el cliente-3 tiene que esperar hasta que todos los servicios para el cliente-2 estén terminados. Tome el escenario donde los miles de clientes necesitan acceder al servidor al mismo tiempo ... entonces todos los clientes tienen que espera (el servidor está ocupado !!).

Esto se evita creando (usando una bifurcación) una copia duplicada exacta (es decir, secundaria) del servidor, donde cada elemento secundario (que es una copia duplicada exacta de su padre, es decir, el servidor) se dedica al cliente recién llegado, por lo que simultáneamente todos los clientes acceden al mismo servidor.


Esta es la razón por la cual los procesos del servidor no deben ser de un solo subproceso, manejando las solicitudes de los clientes consecutivamente cuando se pueden manejar de manera concurrente , por ejemplo, en procesos separados. Pero el modelo de servidor multiproceso puede implementarse fácilmente con un proceso de escucha que acepta solicitudes de clientes y crea un proceso completamente nuevo en el que ejecutar el programa de servicio al cliente. La única ventaja que ofrece la forkllamada que copia el proceso principal es que no tiene que tener dos programas separados, pero tener programas separados (por ejemplo, inetd) puede hacer que el sistema sea más modular.
Scott
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.