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.
-
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.
-
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.
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 hello
y 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.
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,
- ¿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.