2010-04-01 16 views
12

? Sé que fork() vuelve de manera diferente para los procesos secundarios y secundarios, pero no puedo encontrar información sobre cómo sucede esto. ¿Cómo recibe el proceso secundario el valor de retorno 0 del tenedor? ¿Y cuál es la diferencia con respecto a la pila de llamadas? Según tengo entendido, para el padre va más o menos así:¿Cómo devuelve fork() para el proceso secundario

proceso primario - invoca fork -> system_call - llamadas fork -> fork execute - regresa a -> system_call - returns to- -> proceso principal.

¿Qué ocurre en el proceso secundario?

+7

Es más comúnmente llamado proceso de "niño" "padre" y. La gente puede acusarlo de sexismo. – Thomas

Respuesta

21

% hombre tenedor

VALOR DEVUELTO

Upon successful completion, fork() returns a value of 0 to the child 
process and returns the process ID of the child process to the parent 
process. Otherwise, a value of -1 is returned to the parent process, no 
child process is created, and the global variable [errno][1] is set to indi- 
cate the error. 

Lo que pasa es que dentro de la llamada al sistema tenedor, todo el proceso se duplica. Luego, la llamada tenedor en cada devolución. Sin embargo, estos son contextos diferentes ahora, por lo que pueden devolver diferentes códigos de retorno.

Si realmente desea saber cómo funciona en un nivel bajo, siempre puede check the source! El código es un poco confuso si no está acostumbrado a leer el código del núcleo, pero los comentarios en línea dan una buena pista sobre lo que está sucediendo.

La parte más interesante de la fuente con una respuesta explícita a su pregunta es en el final de la definición tenedor() sí -

if (error == 0) { 
    td->td_retval[0] = p2->p_pid; 
    td->td_retval[1] = 0; 
} 

"td" aparentemente mantiene una lista de los valores de retorno de diferentes hilos. No estoy seguro exactamente cómo funciona este mecanismo (por qué no hay dos estructuras de "hilos" separadas). Si el error (devuelto desde fork1, la función de bifurcación "real") es 0 (sin error), tome el "primer" subproceso (principal) y establezca su valor de retorno en p2 (el nuevo proceso) PID. Si es el "segundo" hilo (en p2), establezca el valor de retorno en 0.

+0

¿Es esto sys/kern/kern_fork.c - fuente de Linux? – osgx

+0

@osgx: No, la fuente citada es de FreeBSD. – jxh

0

El proceso parece idéntico desde ambos lados, excepto por el diferente valor de retorno (es por eso que el valor de retorno está allí, por lo que los dos procesos pueden distinguir la diferencia). En lo que respecta al proceso hijo, se lo habrá devuelto desde system_call de la misma manera en que se devolvió el proceso padre.

+0

Sí, pero ¿cómo funciona el valor de los diferentes valores de retorno? ¿Cómo puede una función devolver dos valores diferentes? – EpsilonVector

+0

Como el núcleo del código del kernel que admite 'fork 'se encarga de ello, hay dos retornos separados, y cada uno puede tener un valor diferente. – Amber

7

La llamada al sistema fork() regresa dos veces (a menos que falle).

  • Uno de los retornos es en el proceso hijo, y no el valor de retorno es 0.

  • El otro de retorno es en el proceso primario, y no el valor de retorno no es cero (ya sea negativo si la horquilla falló, o un valor distinto de cero que indica el PID del niño).

Las principales diferencias entre el padre y el niño son:

  • Son procesos separados
  • El valor de PID es diferente
  • El valor de PPID (padre PID) es diferente

Otras diferencias más oscuras se enumeran en el estándar POSIX.

En cierto sentido, el Cómo realmente no es su problema. El sistema operativo es requerido para lograr el resultado. Sin embargo, la o/s clona el proceso principal, realizando un segundo proceso secundario que es una réplica casi exacta de la matriz, configura los atributos que deben ser diferentes a los valores nuevos correctos y, por lo general, marca las páginas de datos como COW (copia en write) o equivalente, de modo que cuando un proceso modifica un valor, obtiene una copia separada de la página para no interferir con el otro. Esto no es como la llamada al sistema obsoleto (por mí, al menos, no estándar para POSIX) vfork() que sería prudente evitar aunque esté disponible en su sistema. Cada proceso continúa después del fork() como si la función regresara, entonces (como dije en la parte superior), la llamada al sistema fork() regresa dos veces, una en cada uno de los dos procesos que son clones casi idénticos entre sí.

+4

Desearía que el fanático de vfork() explicara por qué dieron un voto negativo. –

+0

¡Buena explicación y buen enlace al último estándar POSIX (Número 7)! –

+1

+1 para compensar el vfork fanboy. –

3

respuesta de Steven Schlansker es bastante bueno, pero sólo para añadir algo más de detalle:

Cada proceso en ejecución tiene un contexto asociado (de ahí "el cambio de contexto") - este contexto incluye, entre otras cosas, el segmento de código del proceso (que contiene las instrucciones de la máquina), su memoria de pila, su pila y su contenido de registro. Cuando se produce un cambio de contexto, se guarda el contexto del proceso anterior y se carga el contexto del nuevo proceso.

La ubicación de un valor de retorno está definida por la ABI, para permitir la interoperabilidad del código. Si estoy escribiendo código ASM para mi procesador x86-64, y llamo al tiempo de ejecución C, sé que el valor de retorno aparecerá en el registro RAX.

Poner estas dos cosas juntas, la conclusión lógica es que la llamada a int pid = fork() resultados en dos contextos en los que la siguiente instrucción a ejecutar en cada uno es el que mueve el valor de RAX (el valor devuelto por la llamada fork) en la variable local pid. Por supuesto, solo un proceso puede ejecutarse a la vez en una sola CPU, por lo que el orden en el que ocurren estas "devoluciones" será determinado por el planificador.

1

Intentaré responder desde el punto de vista del diseño de la memoria de proceso. Chicos, por favor corríjanme si algo está mal o es inexacto.

tenedor() es la única llamada al sistema para la creación de procesos (excepto el proceso de principio 0), así que la pregunta es en realidad lo que sucede con proceso de creación en el kernel. Hay dos estructuras de datos del kernel relacionadas con el proceso, struct proc array (también conocido como tabla de proceso) y struct user (aka área u).

Para crear un nuevo proceso, estas dos estructuras de datos deben crearse o parametrizarse correctamente. La forma directa es alinearse con el área del proc & del creador (o del padre). La mayoría de los datos se duplican entre el elemento primario & hijo (p., el segmento de código), excepto los valores en el registro de retorno (por ejemplo, EAX en 80x86), para los cuales padre es con pid's child y child es 0. Desde entonces, tiene dos procesos (uno existente & nuevo) ejecutados por el planificador y luego de la programación, cada uno devolverá sus valores respectivamente.

7

Ambos padres e hijos devuelven valores diferentes debido a la manipulación de los registros de la CPU en el contexto del niño.

Cada proceso en el kernel de Linux representado por task_struct. task_struct está encerrado (puntero) en la estructura thread_info que se encuentra al final de la pila del modo kernel. Todo el contexto de la CPU (registros) se almacena en esta estructura thread_info.

struct thread_info { 
    struct task_struct *task;  /* main task structure */ 
    struct cpu_context_save cpu_context; /* cpu context */ 
} 

Todo sistema de tenedor/clone() llama llamadas kernel equivalente do_fork función().

long do_fork(unsigned long clone_flags, 
      unsigned long stack_start, 
      struct pt_regs *regs, 
      unsigned long stack_size, 
      int __user *parent_tidptr, 
      int __user *child_tidptr) 

Aquí está la secuencia de ejecución

do_fork() -> copy_process-> copy_thread() (copy_thread es arco de llamada a una función específica)

copy_thread() copia los valores de registro de los padres y cambia el valor de retorno a 0 (En caso de brazo)

struct pt_regs *childregs = task_pt_regs(p); 
*childregs = *regs; /* Copy register value from parent process*/ 
childregs->ARM_r0 = 0; /*Change the return value*/ 
thread->cpu_context.sp = (unsigned long)childregs;/*Write back the value to thread info*/ 
thread->cpu_context.pc = (unsigned long)ret_from_fork; 

Cuando se programa al niño, ejecuta una rutina de ensamblaje ret_from_fork() que devolverá cero. Para el padre se pone el valor de retorno de la do_fork() que es pid del proceso

nr = task_pid_vnr(p); 
return nr; 
Cuestiones relacionadas