En muchos programas y páginas de manual de Linux, he visto el uso de código fork()
. ¿Por qué necesitamos usar fork()
y cuál es su propósito?
Respuestas:
fork()
así es como se crean nuevos procesos en Unix. Cuando llama fork
, está creando una copia de su propio proceso que tiene su propio espacio de direcciones . Esto permite que varias tareas se ejecuten independientemente unas de otras como si cada una tuviera la memoria completa de la máquina para sí misma.
Estos son algunos ejemplos de usos de fork
:
fork
para ejecutar los programas que invoca desde la línea de comandos.fork
para crear múltiples procesos de servidor, cada uno de los cuales maneja las solicitudes en su propio espacio de direcciones. Si uno muere o pierde memoria, otros no se ven afectados, por lo que funciona como un mecanismo de tolerancia a fallas.fork
para manejar cada página dentro de un proceso separado. Esto evitará que el código del lado del cliente en una página detenga todo su navegador.fork
se utiliza para generar procesos en algunos programas paralelos (como los escritos con MPI ). Tenga en cuenta que esto es diferente al uso de subprocesos , que no tienen su propio espacio de direcciones y existen dentro de un proceso.fork
indirectamente para iniciar procesos secundarios. Por ejemplo, cada vez que usa un comando como subprocess.Popen
en Python, fork
un hijo procesa y lee su salida. Esto permite que los programas funcionen juntos.El uso típico de fork
en un shell podría verse así:
int child_process_id = fork();
if (child_process_id) {
// Fork returns a valid pid in the parent process. Parent executes this.
// wait for the child process to complete
waitpid(child_process_id, ...); // omitted extra args for brevity
// child process finished!
} else {
// Fork returns 0 in the child process. Child executes this.
// new argv array for the child process
const char *argv[] = {"arg1", "arg2", "arg3", NULL};
// now start executing some other program
exec("/path/to/a/program", argv);
}
El shell genera un proceso hijo exec
y espera a que se complete, luego continúa con su propia ejecución. Tenga en cuenta que no tiene que usar fork de esta manera. Siempre puede generar muchos procesos secundarios, como podría hacer un programa paralelo, y cada uno puede ejecutar un programa al mismo tiempo. Básicamente, cada vez que crea nuevos procesos en un sistema Unix, está usando fork()
. Para el equivalente de Windows, eche un vistazo a CreateProcess
.
Si desea más ejemplos y una explicación más extensa, Wikipedia tiene un resumen decente. Y aquí hay algunas diapositivas sobre cómo funcionan los procesos, los subprocesos y la concurrencia en los sistemas operativos modernos.
fork()
es la manera de crear un nuevo proceso en UNIX, pero para ser pedante, hay al menos otro: posix_spawn()
.
fork () es cómo Unix crea nuevos procesos. En el punto en el que llamó a fork (), su proceso se clona y dos procesos diferentes continúan la ejecución desde allí. Uno de ellos, el hijo, hará que fork () devuelva 0. El otro, el padre, hará que fork () devuelva el PID (ID de proceso) del hijo.
Por ejemplo, si escribe lo siguiente en un shell, el programa de shell llamará a fork () y luego ejecutará el comando que pasó (telnetd, en este caso) en el hijo, mientras que el padre mostrará el indicador nuevamente, también como un mensaje que indica el PID del proceso en segundo plano.
$ telnetd &
En cuanto a la razón por la que crea nuevos procesos, así es como su sistema operativo puede hacer muchas cosas al mismo tiempo. Es por eso que puede ejecutar un programa y, mientras se está ejecutando, cambiar a otra ventana y hacer otra cosa.
fork () se usa para crear un proceso hijo. Cuando se llama a una función fork (), se generará un nuevo proceso y la llamada a la función fork () devolverá un valor diferente para el hijo y el padre.
Si el valor de retorno es 0, sabe que es el proceso secundario y si el valor de retorno es un número (que resulta ser el ID del proceso secundario), sabe que es el padre. (y si es un número negativo, la bifurcación falló y no se creó ningún proceso hijo)
fork () se usa básicamente para crear un proceso hijo para el proceso en el que está llamando a esta función. Siempre que llame a un fork (), devuelve un cero para la identificación del niño.
pid=fork()
if pid==0
//this is the child process
else if pid!=0
//this is the parent process
con esto, puede proporcionar diferentes acciones para el padre y el hijo y hacer uso de la función de subprocesos múltiples.
fork () creará un nuevo proceso hijo idéntico al padre. Entonces, todo lo que ejecute en el código después de eso será ejecutado por ambos procesos, muy útil si tiene, por ejemplo, un servidor y desea manejar múltiples solicitudes.
Probablemente no necesite usar fork en la programación diaria si está escribiendo aplicaciones.
Incluso si desea que su programa inicie otro programa para realizar alguna tarea, existen otras interfaces más simples que usan fork entre bastidores, como "system" en C y perl.
Por ejemplo, si desea que su aplicación inicie otro programa como bc para hacer algunos cálculos por usted, puede usar 'system' para ejecutarlo. El sistema hace una 'bifurcación' para crear un nuevo proceso, luego un 'ejecutivo' para convertir ese proceso en bc. Una vez que bc se completa, el sistema devuelve el control a su programa.
También puede ejecutar otros programas de forma asincrónica, pero no recuerdo cómo.
Si está escribiendo servidores, shells, virus o sistemas operativos, es más probable que desee utilizar fork.
system()
. Estaba leyendo fork()
porque quiero que mi código C ejecute un script de Python.
La llamada al sistema fork () se usa para crear procesos. No toma argumentos y devuelve un ID de proceso. El propósito de fork () es crear un nuevo proceso, que se convierte en el proceso hijo de la persona que llama. Después de que se crea un nuevo proceso hijo, ambos procesos ejecutarán la siguiente instrucción después de la llamada al sistema fork (). Por tanto, tenemos que distinguir al padre del hijo. Esto se puede hacer probando el valor devuelto de fork ():
Si fork () devuelve un valor negativo, la creación de un proceso hijo no tuvo éxito. fork () devuelve un cero al proceso hijo recién creado. fork () devuelve un valor positivo, el ID de proceso del proceso hijo, al padre. El ID de proceso devuelto es del tipo pid_t definido en sys / types.h. Normalmente, el ID del proceso es un número entero. Además, un proceso puede usar la función getpid () para recuperar el ID de proceso asignado a este proceso. Por lo tanto, después de la llamada del sistema a fork (), una prueba simple puede decir qué proceso es el hijo. Tenga en cuenta que Unix hará una copia exacta del espacio de direcciones de los padres y se lo dará al niño. Por lo tanto, los procesos padre e hijo tienen espacios de direcciones separados.
Entendamos con un ejemplo para aclarar los puntos anteriores. Este ejemplo no distingue los procesos padre e hijo.
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#define MAX_COUNT 200
#define BUF_SIZE 100
void main(void)
{
pid_t pid;
int i;
char buf[BUF_SIZE];
fork();
pid = getpid();
for (i = 1; i <= MAX_COUNT; i++) {
sprintf(buf, "This line is from pid %d, value = %d\n", pid, i);
write(1, buf, strlen(buf));
}
}
Suponga que el programa anterior se ejecuta hasta el punto de la llamada a fork ().
Si la llamada a fork () se ejecuta con éxito, Unix hará dos copias idénticas de espacios de direcciones, una para el padre y la otra para el hijo. Ambos procesos comenzarán su ejecución en la siguiente instrucción que sigue a la llamada a fork (). En este caso, ambos procesos iniciarán su ejecución en la asignación
pid = .....;
Ambos procesos comienzan su ejecución justo después de que el sistema llame a fork (). Dado que ambos procesos tienen espacios de direcciones idénticos pero separados, esas variables inicializadas antes de la llamada a fork () tienen los mismos valores en ambos espacios de direcciones. Dado que cada proceso tiene su propio espacio de direcciones, cualquier modificación será independiente de las demás. En otras palabras, si el padre cambia el valor de su variable, la modificación solo afectará a la variable en el espacio de direcciones del proceso padre. Otros espacios de direcciones creados por llamadas a fork () no se verán afectados aunque tengan nombres de variables idénticos.
¿Cuál es la razón de usar write en lugar de printf? Es porque printf () está "almacenado", lo que significa que printf () agrupará la salida de un proceso. Mientras almacena en búfer la salida del proceso principal, el hijo también puede usar printf para imprimir cierta información, que también se almacenará en búfer. Como resultado, dado que la salida no se enviará a la pantalla inmediatamente, es posible que no obtenga el orden correcto del resultado esperado. Peor aún, la salida de los dos procesos puede mezclarse de formas extrañas. Para superar este problema, puede considerar utilizar la escritura "sin búfer".
Si ejecuta este programa, es posible que vea lo siguiente en la pantalla:
................
This line is from pid 3456, value 13
This line is from pid 3456, value 14
................
This line is from pid 3456, value 20
This line is from pid 4617, value 100
This line is from pid 4617, value 101
................
This line is from pid 3456, value 21
This line is from pid 3456, value 22
................
El ID de proceso 3456 puede ser el asignado al padre o al hijo. Debido al hecho de que estos procesos se ejecutan al mismo tiempo, sus líneas de salida se entremezclan de una manera bastante impredecible. Además, el orden de estas líneas lo determina el planificador de la CPU. Por lo tanto, si ejecuta este programa nuevamente, puede obtener un resultado totalmente diferente.
El multiprocesamiento es fundamental para la informática. Por ejemplo, su IE o Firefox puede crear un proceso para descargar un archivo mientras todavía está navegando por Internet. O, mientras imprime un documento en un procesador de texto, aún puede ver diferentes páginas y aún hacer algunas modificaciones con él.
Fork () se usa para crear nuevos procesos como todo el mundo ha escrito.
Aquí está mi código que crea procesos en forma de árbol binario ... Le pedirá escanear el número de niveles hasta los que desea crear procesos en árbol binario
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
int main()
{
int t1,t2,p,i,n,ab;
p=getpid();
printf("enter the number of levels\n");fflush(stdout);
scanf("%d",&n);
printf("root %d\n",p);fflush(stdout);
for(i=1;i<n;i++)
{
t1=fork();
if(t1!=0)
t2=fork();
if(t1!=0 && t2!=0)
break;
printf("child pid %d parent pid %d\n",getpid(),getppid());fflush(stdout);
}
waitpid(t1,&ab,0);
waitpid(t2,&ab,0);
return 0;
}
SALIDA
enter the number of levels
3
root 20665
child pid 20670 parent pid 20665
child pid 20669 parent pid 20665
child pid 20672 parent pid 20670
child pid 20671 parent pid 20670
child pid 20674 parent pid 20669
child pid 20673 parent pid 20669
Primero hay que entender qué es la llamada al sistema fork (). Dejame explicar
La llamada al sistema fork () crea el duplicado exacto del proceso principal. Hace el duplicado de la pila principal, el montón, los datos inicializados, los datos no inicializados y comparte el código en modo de solo lectura con el proceso principal.
La llamada al sistema de la bifurcación copia la memoria sobre la base de copia sobre escritura, significa que el niño hace en la página de memoria virtual cuando hay un requisito de copia.
Ahora el propósito de fork ():
fork()
se utiliza para generar un proceso hijo. Por lo general, se usa en situaciones similares a las de los subprocesos, pero existen diferencias. A diferencia de los hilos, fork()
crea procesos completamente separados, lo que significa que el niño y el padre, aunque son copias directas el uno del otro en el punto que fork()
se llama, están completamente separados, ninguno puede acceder al espacio de memoria del otro (sin pasar a los problemas normales vas a acceder a la memoria de otro programa).
fork()
todavía lo utilizan algunas aplicaciones de servidor, principalmente las que se ejecutan como root en una máquina * NIX que eliminan los permisos antes de procesar las solicitudes de los usuarios. Todavía hay algunos otros casos de uso, pero la mayoría de las personas se han trasladado ahora al subproceso múltiple.
La razón de ser de fork () versus simplemente tener una función exec () para iniciar un nuevo proceso se explica en una respuesta a una pregunta similar en el intercambio de pila de Unix .
Esencialmente, dado que fork copia el proceso actual, todas las opciones posibles para un proceso se establecen de forma predeterminada, por lo que el programador no tiene que proporcionarlas.
En el sistema operativo Windows, por el contrario, los programadores tienen que usar la función CreateProcess que es MUCHO más complicada y requiere poblar una estructura múltiple para definir los parámetros del nuevo proceso.
Entonces, en resumen, la razón para bifurcar (versus ejecutar) es la simplicidad en la creación de nuevos procesos.
Uso de la llamada al sistema Fork () para crear un proceso hijo. Es un duplicado exacto del proceso principal. La bifurcación copia la sección de pila, la sección de montón, la sección de datos, la variable de entorno, los argumentos de la línea de comandos del padre.
La función fork () se utiliza para crear un nuevo proceso duplicando el proceso existente desde el que se llama. El proceso existente desde el que se llama a esta función se convierte en el proceso padre y el proceso recién creado se convierte en el proceso hijo. Como ya se indicó, el niño es una copia duplicada del padre, pero hay algunas excepciones.
El niño tiene un PID único como cualquier otro proceso que se ejecute en el sistema operativo.
El niño tiene un ID de proceso principal que es el mismo que el PID del
proceso que lo creó.
Los contadores de tiempo de CPU y de utilización de recursos se ponen a cero en el proceso hijo.
El conjunto de señales pendientes en el niño está vacío.
El niño no hereda ningún temporizador de su padre
Ejemplo:
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
int var_glb; /* A global variable*/
int main(void)
{
pid_t childPID;
int var_lcl = 0;
childPID = fork();
if(childPID >= 0) // fork was successful
{
if(childPID == 0) // child process
{
var_lcl++;
var_glb++;
printf("\n Child Process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
}
else //Parent process
{
var_lcl = 10;
var_glb = 20;
printf("\n Parent process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
}
}
else // fork failed
{
printf("\n Fork failed, quitting!!!!!!\n");
return 1;
}
return 0;
}
Ahora, cuando se compila y ejecuta el código anterior:
$ ./fork
Parent process :: var_lcl = [10], var_glb[20]
Child Process :: var_lcl = [1], var_glb[1]