2012-07-31 30 views
28

En "Programación avanzada en el entorno Unix", 2da edición, por W. Richard Stevens.¿Alguien puede explicar una descripción simple con respecto al 'descriptor de archivo' después de fork()?

Sección 8.3 Función de horquilla.

Aquí está la descripción:

Es importante que el padre y el hijo comparten el mismo archivo offset.

Considere un proceso que bifurca a un niño, luego espera a que el niño complete. Suponga que ambos procesos escriben en salida estándar como parte de su procesamiento normal. Si el padre tiene su salida estándar redirigida (por un intérprete de comandos, tal vez), es esencial que el niño actualice la compensación del archivo del padre cuando el niño escribe en la salida estándar.

[1. Qué significa eso? si la salida estándar de los padres se redirecciona a un 'archivo1', por ejemplo, ¿qué debe actualizar el hijo después de que el niño escriba? el desplazamiento de salida estándar original de los padres o el desplazamiento de salida redirigido (es decir, el archivo1)? No puede ser el último, ¿no?]

[2. ¿Cómo se realiza la actualización? por niño explícitamente, por SO implícitamente, por el descriptor de archivos en sí? Después de la bifurcación, pensé que padre e hijo siguieron sus propios caminos y tienen su propia COPIA del descriptor de archivo. Entonces, ¿cómo se actualiza la actualización infantil al lado principal?]

En este caso, el niño puede escribir en la salida estándar mientras el padre está esperando; al finalizar el niño, el padre puede continuar escribiendo en la salida estándar, sabiendo que su salida se agregará a lo que sea que el niño haya escrito. Si el padre y el hijo no compartían el mismo desfase de archivos, este tipo de interacción sería más difícil de lograr y requeriría acciones explícitas del padre.

Si el padre y el hijo escriben en el mismo descriptor, sin ninguna forma de sincronización, como que el padre espere al hijo, su salida se entremezclará (suponiendo que sea un descriptor abierto antes de la bifurcación). Aunque esto es posible, no es el modo normal de operación.

Hay dos casos normales para manejar los descriptores después de un tenedor.

  1. El padre espera a que el niño complete. En este caso, el padre no necesita hacer nada con sus descriptores. Cuando el niño finaliza, cualquiera de los descriptores compartidos que el niño leyó o escribió tendrá sus correcciones de archivos actualizadas en consecuencia.

  2. Tanto el padre como el hijo siguen sus propios caminos. Aquí, después del tenedor, el padre cierra los descriptores que no necesita, y el niño hace lo mismo. De esta forma, ninguno interfiere con las descripciones abiertas del otro. Este escenario es a menudo el caso de los servidores de red."

[3. Cuando se invoca tenedor(), lo único que entiendo es que los niños obtener una copia de lo que los padres tiene, descriptor de archivo, en este caso, y hace lo suyo. Si cualquier desplazamiento cambia al descriptor de archivo que padre e hijo comparten, solo puede ser porque el descriptor recuerda el desplazamiento en sí. ¿Estoy en lo cierto?]

Lo siento, soy un poco nuevo en los conceptos .

¿Algún ayuda? Gracias.

Respuesta

67

Es importante distinguir entre el descriptor archivo, que es un entero pequeño que el proceso utiliza en sus lectura y escritura de llamadas para identificar el archivo y la descripción archivo, que es una estructura en el núcleo. El desplazamiento del archivo es parte de la descripción del archivo. Vive en el kernel.

A modo de ejemplo, vamos a utilizar este programa:

#include <unistd.h> 
#include <fcntl.h> 
#include <sys/wait.h> 

int main(void) 
{ 
    int fd; 

    fd = open("output", O_CREAT|O_TRUNC|O_WRONLY, 0666); 

    if(!fork()) { 
     /* child */ 
     write(fd, "hello ", 6); 
     _exit(0); 
    } else { 
     /* parent */ 
     int status; 

     wait(&status); 
     write(fd, "world\n", 6); 
    } 
} 

(Todo comprobación de errores se ha omitido)

Si compilamos este programa, lo llaman hello, y correr así:

./hello 

aquí es lo que sucede:

El programa abierto s el archivo output, creándolo si no existe o truncarlo a tamaño cero si existiera. El kernel crea una descripción de archivo (en el kernel de Linux esto es un struct file) y lo asocia con un descriptor de archivo para el proceso de llamada (el entero no negativo más bajo que no está en uso en la tabla de descriptores de archivo de ese proceso). El descriptor de archivo se devuelve y se asigna al fd en el programa. En aras del argumento, supongamos que fd es 3.

El programa hace una horquilla(). El nuevo proceso secundario obtiene una copia de la tabla de descriptores de archivos de sus padres, pero la descripción del archivo no se copia. La entrada número 3 en las tablas de archivos de ambos procesos apunta al mismo struct file.

El proceso principal espera mientras el proceso secundario escribe. La escritura del niño hace que la primera mitad de "hello world\n" se almacene en el archivo y adelanta el desplazamiento del archivo en 6. El desplazamiento del archivo se encuentra en el struct file.

El hijo sale, el padre wait() termina, y el padre escribe, usando fd 3 que todavía está asociado con la misma descripción de archivo que tuvo su desplazamiento de archivo actualizado por el write() del niño. Por lo tanto, la segunda mitad del mensaje se almacena después de la primera parte, sin sobreescribirlo como lo hubiera hecho si el padre tuviera un desfase de archivo cero, que sería el caso si la descripción del archivo no se compartiera.

Finalmente el padre sale, y el kernel ve que el struct file ya no está en uso y lo libera.

+1

Alan, ¡la explicación es genial! Gracias por tomarse el tiempo y explicarlo con tanto detalle para un novato. – user1559625

+1

@AlanCurry ¿Qué sucede si no uso wait() en el programa principal?¿La descripción del archivo sería libre a medida que el proceso principal salga? – Nmzzz

+0

¡servicial! ¡gracias! – Kross

3

En la misma sección, del libro, hay un diagrama que muestra tres tablas que están allí cuando se abre un archivo.

La tabla del descriptor de archivo de usuario (parte de la entrada de la tabla de proceso), la tabla de archivos de archivos y la tabla de inode (tabla v-node). Ahora una entrada filedescriptor (que es un índice de la tabla de descriptores de archivos) apunta a una entrada de tabla de archivos, que apunta a una entrada de tabla de inode.
Ahora el offset de archivo (la posición desde donde se realiza la siguiente lectura/escritura) está en la tabla de archivos .

Digamos que tiene un archivo abierto en el elemento primario, es decir, tiene un descriptor, un archivo y también una referencia de inodo.
Ahora, cuando está creando un elemento secundario, la tabla de descriptores de archivos se copia para el elemento secundario. Por lo tanto, el recuento de referencias en la entrada de la tabla de archivos (para ese descriptor abierto) aumenta, lo que significa que ahora hay dos referencias para la misma entrada en la tabla de archivos.

Este descriptor ahora está disponible tanto en padres como en hijos, apuntando a la misma entrada en la Tabla de archivos, compartiendo el desplazamiento. Ahora que tiene estos antecedentes nos deja ver sus preguntas,

  1. ¿Qué significa? si la salida estándar de los padres se redirecciona a un 'archivo1', por ejemplo, ¿qué debe actualizar el hijo después de que el niño escriba? el desplazamiento de salida estándar original de los padres o el desplazamiento de salida redirigido (es decir, el archivo1)? No puede ser el último, ¿verdad?]

El niño no necesita actualizar nada explícitamente. El autor del libro está tratando de
decir eso, supongamos que la salida estándar de los padres se redirige a un archivo y se realiza una llamada de tenedor. Después de eso, el padre está esperando. Por lo tanto, el descriptor ahora está duplicado, es decir, el desplazamiento del archivo también se comparte. Ahora, cada vez que el niño escribe algo para la salida estándar, los datos escritos se guardan en el archivo redirigido. El desplazamiento se incrementa automáticamente mediante la llamada de escritura.

Ahora diga que el niño sale. Entonces el padre sale de esperar y escribe algo en la salida estándar (que es redirigido). Ahora donde se colocará la salida de la llamada de escritura del padre -> después de los datos, que fue escrita por el niño. Por qué -> ya que el valor actual de la compensación ahora se cambia después de que el niño haya escrito.

Parent () 
    { 
    open a file for writing, that is get the 
    descriptor(say fd); 
    close(1);//Closing stdout 
    dup(fd); //Now writing to stdout means writing to the file 
    close(fd) 
     //Create a child that is do a fork call. 
    ret = fork(); 
    if (0 == ret) 
    { 
     write(1, "Child", strlen("Child"); 
     exit .. 
    } 
     wait(); //Parent waits till child exit. 

     write(1, "Parent", strlen("Parent"); 
    exit .. 
} 

Pl. vea el pseudo código anterior, los datos finales que contiene el archivo abierto serán ChildParent. Por lo tanto, puede ver que la compensación del archivo cambió cuando el niño escribió y esto estuvo disponible para la llamada de escritura del padre, ya que el más reciente se compartió.

2.¿Cómo se hace la actualización? por niño explícitamente, por SO implícitamente, por el descriptor de archivos en sí? Después de la bifurcación, pensé que padre e hijo siguieron sus propios caminos y tiene su propia COPIA del descriptor de archivo. Entonces, ¿cómo se actualiza la actualización infantil al lado principal?]

Now I think the answer is clear-> by the system call that is by the OS. 

[3. Cuando se invoca fork(), todo lo que entiendo es que el niño obtiene una COPIA de lo que tiene el padre, el descriptor de archivo en este caso, y lo hace. Si cualquier desplazamiento cambia al descriptor de archivo que comparten el padre y el hijo, solo puede ser porque el descriptor recuerda el desplazamiento en sí. ¿Estoy en lo cierto?]

Esto también debe quedar claro. La entrada de la tabla de archivos de usuario apunta a la tabla de archivos entrada de tabla (que contiene el desplazamiento).

En otras palabras, las llamadas al sistema pueden obtener el desplazamiento del descriptor.

+0

"el recuento de referencias en la entrada de la tabla de archivos (para ese descriptor abierto) se incrementa". ¿Puedes señalar una referencia para esto? Significa que las copias en horquilla de la memoria principal no son una copia poco profunda. – dashesy

Cuestiones relacionadas