Buscando una descripción simple con respecto al ‘descriptor de archivo’ después de fork ()

10 minutos de lectura

avatar de usuario
usuario1559625

En “Programación avanzada en el entorno Unix”, 2ª edición, por W. Richard Stevens. Sección 8.3 Función de horquilla.

Aquí está la descripción:

Es importante que el padre y el hijo compartan el mismo desplazamiento de archivo.

Considere un proceso que bifurca a un niño, luego espera a que el niño se complete. Suponga que ambos procesos escriben en la salida estándar como parte de su procesamiento normal. Si el padre tiene su salida estándar redirigida (quizás por un shell), es esencial que el hijo actualice el desplazamiento del archivo del padre cuando el hijo escribe en la salida estándar.

Mis respuestas:

{1} ¿Qué significa? si la salida estándar de los padres se redirige a un ‘archivo 1’, por ejemplo, ¿qué debe actualizar el niño después de que el niño escriba? ¿Compensación de salida estándar original de los padres o compensación de salida redirigida (es decir, archivo1)? No puede ser más tarde, ¿verdad?

{2} ¿Cómo se realiza la actualización? por child explícitamente, por OS implícitamente, por el descriptor de archivos en sí? Después de la bifurcación, pensé que padre e hijo siguieron su propio camino y tienen su propia COPIA del descriptor de archivo. Entonces, ¿cómo actualiza el niño el desplazamiento al lado del padre?

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

Si tanto el padre como el hijo escriben en el mismo descriptor, sin ninguna forma de sincronización, como hacer que el padre espere al hijo, su salida se entremezclará (suponiendo que sea un descriptor que estaba 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 una bifurcación.

  1. El padre espera a que el hijo termine. 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 desplazamientos de archivo actualizados en consecuencia.

  2. Tanto el padre como el niño siguen sus propios caminos. Aquí, después de la bifurcación, el padre cierra los descriptores que no necesita y el hijo hace lo mismo. De esta manera, ninguno interfiere con los descriptores abiertos del otro. Este escenario suele ser el caso de los servidores de red.

Mi respuesta:

{3} Cuando se invoca fork(), todo lo que entiendo es que el niño obtiene una COPIA de lo que tiene el padre, descriptor de archivo en este caso, y hace lo suyo. Si algún desplazamiento cambia en el descriptor de archivo que comparten el padre y el hijo, solo puede deberse a que el descriptor recuerda el desplazamiento en sí. ¿Tengo razón?

Soy un poco nuevo en los conceptos.

avatar de usuario
alan curry

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

Como ejemplo, usemos 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);
    }
}

(Se ha omitido toda comprobación de errores)

Si compilamos este programa, llámalo helloy ejecútalo así:

./hello

esto es lo que sucede:

El programa abre la output archivo, creándolo si aún no existía o truncándolo a tamaño cero si existía. 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 número entero no negativo más bajo que aún no está en uso en la tabla de descriptores de archivo de ese proceso). El descriptor de archivo se devuelve y se asigna a fd en el programa. En aras del argumento, supongamos que fd es 3

El programa hace una bifurcación(). El nuevo proceso hijo obtiene un Copiar de la tabla de descriptores de archivo de su padre, pero la descripción del archivo no se copia. La entrada número 3 en las tablas de archivo 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" que se almacenará en el archivo, y avanza el desplazamiento del archivo en 6. El desplazamiento del archivo está en el struct file!

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

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

  • Alan, la explicación es genial! Gracias por tomarte el tiempo y explicarlo con tanto detalle para un novato.

    – usuario1559625

    31 de julio de 2012 a las 6:55

  • @AlanCurry ¿Qué sucede si no uso wait() en el programa principal? ¿La descripción del archivo sería libre al salir del proceso principal?

    – Nmzzz

    15 de febrero de 2014 a las 8:10

  • “obtiene una copia de la tabla de descriptores de archivo de su padre, pero la descripción del archivo no se copia”. ¿Es este un comportamiento específico para los descriptores de archivos (el hecho de que se comparte la descripción del núcleo)? Porque las variables globales, por ejemplo, se copian, no se comparten, afaik.

    – Joel Blum

    29 de julio de 2018 a las 9:51

  • @Allan Curry, ¿por qué llamas? _exit en lugar de exit? ¿Podría explicar las relaciones con su respuesta, por favor?

    – jyz

    22 de agosto de 2021 a las 16:42


avatar de usuario
Tanmoy Bandyopadhyay

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 de descriptores de archivos del usuario (parte de la entrada de la tabla de procesos), la tabla de archivos y la tabla de inodos (tabla de nodos v). Ahora, una entrada de descriptor de archivo (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 inodos.
Ahora el desplazamiento de archivo (la posición desde donde ocurre la siguiente lectura/escritura) está allí en el tabla de archivos.

Entonces, supongamos que tiene un archivo abierto en el padre, eso significa que tiene un descriptor, una entrada en la tabla de archivos y también una referencia de inodo.
Ahora, cuando está creando un niño, la tabla de descriptores de archivo se copia para el niño. Entonces, 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 de la tabla de archivos.

Este descriptor ahora está disponible tanto en el padre como en el hijo, y apunta a la misma entrada de la tabla de archivos, por lo que comparte el desplazamiento.
Ahora que tenemos estos antecedentes, déjanos ver tus preguntas,

  1. ¿Qué significa? si la salida estándar de los padres se redirige a un ‘archivo 1’, por ejemplo, ¿qué debe actualizar el niño después de que el niño escriba? ¿Compensación de salida estándar original de los padres o compensación de salida redirigida (es decir, archivo1)? No puede ser más tarde, ¿verdad?]

El niño explícitamente no necesita actualizar nada. 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 bifurcación. Después de eso, el padre está esperando. Entonces, el descriptor ahora está duplicado, es decir, el desplazamiento del archivo también se comparte. Ahora, cada vez que el niño escribe algo de forma estándar, los datos escritos se guardan en el archivo redirigido.
El desplazamiento se incrementa automáticamente por la llamada de escritura.

Ahora diga que el niño sale. Entonces, el padre sale de la espera y escribe algo en la salida estándar (que se redirige). Ahora, donde se colocará la salida de la llamada de escritura del padre -> después de los datos, que fue escrito por el hijo. Por qué -> ya que el valor actual del desplazamiento ahora 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 pseudocódigo anterior, los datos finales que contiene el archivo abierto serán ChildParent. Entonces, ve que el desplazamiento del archivo cambió cuando el niño escribió y esto estaba disponible para la llamada de escritura del padre, ya que el desplazamiento es compartido.

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

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

[3. When fork() is invoked, all i understand is that child get a COPY of what parent has,
file descriptor in this case, and does its thing. If any offset changes to file descriptor that parent and child share, it can only be because the descriptor remember the offset itself. Am i right?]

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

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

  • “el recuento de referencias en la entrada de la tabla de archivos (para ese descriptor abierto) aumenta”. ¿Puedes señalar una referencia para esto? Significa que las copias bifurcadas de la memoria principal no son una copia superficial.

    – deslumbrante

    23 de junio de 2017 a las 17:49

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad