En el Programa 1 Hello world
se imprime solo una vez, pero cuando lo elimino \n
y lo ejecuto (Programa 2), la salida se imprime 8 veces. ¿Alguien puede explicarme el significado de \n
aquí y cómo afecta al fork()
?
Programa 1
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("hello world...\n");
fork();
fork();
fork();
}
Salida 1:
hello world...
Programa 2
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("hello world...");
fork();
fork();
fork();
}
Salida 2:
hello world... hello world...hello world...hello world...hello world...hello world...hello world...hello world...
./prog1 > prog1.out
) o una tubería (./prog1 | cat
). Prepárate para alucinar. :-) fork()
de almacenamiento en búfer son un encuentro común también en los scripts de shell, y también son algo específicos de Unix, por lo que parece que esto es bastante sobre el tema para Unix.SE.Respuestas:
Cuando se envía a la salida estándar utilizando la
printf()
función de la biblioteca C , la salida generalmente se almacena en búfer. El búfer no se vacía hasta quefflush(stdout)
salga una nueva línea, llame o salga del programa (_exit()
aunque no mediante una llamada ). El flujo de salida estándar se almacena de manera predeterminada en línea de esta manera cuando está conectado a un TTY.Cuando se bifurca el proceso en el "Programa 2", los procesos secundarios heredan cada parte del proceso principal, incluido el búfer de salida no vaciado. Esto copia efectivamente el búfer no vaciado a cada proceso secundario.
Cuando finaliza el proceso, las memorias intermedias se vacían. Comienza un gran total de ocho procesos (incluido el proceso original), y el búfer no vaciado se vaciará al finalizar cada proceso individual.
Es ocho porque en cada
fork()
uno obtienes el doble del número de procesos que tenías antesfork()
(ya que son incondicionales), y tienes tres de estos (2 3 = 8).fuente
main
con_exit(0)
solo hacer una llamada al sistema de salida sin borrar los buffers, y luego se imprimirá cero veces sin una nueva línea. ( La implementación de syscall de exit () y ¿Cómo es que _exit (0) (salir por syscall) me impide recibir ningún contenido estándar? ). O puede canalizar Program1cat
o redirigirlo a un archivo y ver que se imprima 8 veces. (stdout está completamente protegido por defecto cuando no es un TTY). O agregue unfflush(stdout)
al caso de no nueva línea antes del 2dofork()
...No afecta el tenedor de ninguna manera.
En el primer caso, terminas con 8 procesos sin nada que escribir, porque el búfer de salida ya estaba vacío (debido a
\n
).En el segundo caso, todavía tiene 8 procesos, cada uno con un búfer que contiene "Hola mundo ..." y el búfer se escribe al final del proceso.
fuente
@Kusalananda explicó por qué se repite la salida . Si tiene curiosidad por qué la salida se repite 8 veces y no solo 4 veces (el programa base + 3 bifurcaciones):
fuente
El trasfondo importante aquí es que
stdout
se requiere que el estándar proteja la línea como configuración predeterminada.Esto hace que
\n
a vacíe la salida.Como el segundo ejemplo no contiene la nueva línea, la salida no se vacía y, como
fork()
copia todo el proceso, también copia el estado delstdout
búfer.Ahora, estas
fork()
llamadas en su ejemplo crean 8 procesos en total, todos ellos con una copia del estado delstdout
búfer.Por definición, todos estos procesos llaman
exit()
cuando regresanmain()
yexit()
llamadasfflush()
seguidas defclose()
todas las secuencias stdio activas . Esto incluyestdout
y, como resultado, ve el mismo contenido ocho veces.Es una buena práctica llamar
fflush()
a todas las secuencias con salida pendiente antes de llamarfork()
o dejar que la llamada secundaria bifurcada explícitamente_exit()
solo salga del proceso sin vaciar las secuencias stdio.Tenga en cuenta que las llamadas
exec()
no eliminan las memorias intermedias estándar, por lo que está bien no preocuparse por las memorias intermedias si usted (después de llamarfork()
) llamaexec()
y (si eso falla)_exit()
.Por cierto: para comprender que el almacenamiento en búfer incorrecto puede causar, aquí hay un error anterior en Linux que se ha solucionado recientemente:
El estándar requiere
stderr
que nostderr
esté almacenado en búfer de manera predeterminada, pero Linux lo ignoró e hizo que la línea esté en búfer y (aún peor) esté completamente en búfer en caso de que stderr fuera redirigido a través de una tubería. Entonces, los programas escritos para UNIX produjeron cosas sin nueva línea demasiado tarde en Linux.Vea el comentario a continuación, parece estar solucionado ahora.
Esto es lo que hago para solucionar este problema de Linux:
Este código no daña en otras plataformas, ya que llamar
fflush()
a una transmisión que se acaba de vaciar es un noop.fuente
setbuf()
Debian ( la de man7.org se parece ) dice que "el flujo de error estándar stderr siempre está sin búfer por defecto". y una prueba simple parece actuar de esa manera, independientemente de si la salida va a un archivo, una tubería o un terminal. ¿Tiene alguna referencia de qué versión de la biblioteca C haría de otra manera?