Canalizaciones con nombre, descriptores de archivo y EOF

10

Dos ventanas, mismo usuario, con mensajes de bash. En la ventana-1 escriba:

$ mkfifo f; exec <f

Entonces bash ahora está intentando leer desde el descriptor de archivo 0, que se asigna a la canalización con nombre f. En la ventana-2, escriba:

$ echo ls > f

Ahora la ventana-1 imprime un ls y luego el caparazón muere. ¿Por qué?

Siguiente experimento: abra la ventana-1 nuevamente con exec <f. En la ventana-2, escriba:

$ exec 3>f
$ echo ls >&3

Después de la primera línea anterior, la ventana 1 se activa e imprime un mensaje. ¿Por qué? Después de la segunda línea anterior, la ventana-1 imprime la lssalida y el shell permanece vivo. ¿Por qué? De hecho, ahora en window-2, echo ls > fno cierra el shell de window-1.

¡La respuesta debe tener que ver con la existencia del descriptor de archivo 3 de la ventana-2 que hace referencia a la tubería con nombre?

Fixee
fuente
1
Después exec <f, bashno está tratando de leer a partir f, primero se intenta abrir la misma. El open()no regresará hasta que haya algún proceso de hacer otro abierto en el modo de escritura en el tubo (momento en el que el tubo se crea una instancia, y la cáscara leerá de entrada de ella).
Stéphane Chazelas
Excelente punto, @ StéphaneChazelas. Esta debe ser la razón por la cual, una vez que exec 3>fse ejecuta, el primer shell emite un aviso. (Punto menor, ¿quiso decir "en modo de escritura " en su comentario?)
Fixee
1
si, lo siento. Editado ahora justo antes de la fecha límite de 5 minutos
Stéphane Chazelas

Respuestas:

12

Tiene que ver con el cierre del descriptor de archivo.

En su primer ejemplo, echoescribe en su flujo de salida estándar que el shell abre para conectarlo f, y cuando termina, su descriptor se cierra (por el shell). En el extremo receptor, el shell, que lee la entrada de su flujo de entrada estándar (conectado a f) lee ls, se ejecuta lsy luego termina debido a la condición de fin de archivo en su entrada estándar.

La condición de fin de archivo se produce porque todos los escritores de la canalización con nombre (solo uno en este ejemplo) han cerrado su final de la canalización.

En su segundo ejemplo, exec 3>fabre el descriptor de archivo 3 para escribir f, luego echoescribe lsen él. Es el shell que ahora tiene abierto el descriptor de archivo, no el echocomando. El descriptor permanece abierto hasta que lo haga exec 3>&-. En el extremo receptor, el shell, que lee la entrada de su flujo de entrada estándar (conectado a f) lee ls, se ejecuta lsy luego espera más entrada (ya que el flujo aún está abierto).

El flujo permanece abierto porque todos los escritores (el shell, la vía exec 3>fy echo) no han cerrado su extremo de la tubería ( exec 3>ftodavía está en vigor).


He escrito sobre lo echoanterior como si fuera un comando externo. Lo más probable es que esté integrado en el caparazón. Sin embargo, el efecto es el mismo.

Kusalananda
fuente
6

No hay mucho: cuando no hay escritores en la tubería, parece cerrado para los lectores, es decir, devuelve EOF cuando se lee y se bloquea cuando se abre.

Desde la página de manual de Linux ( pipe(7)pero vea también fifo(7)):

Si todos los descriptores de archivo que se refieren al final de escritura de una tubería se han cerrado, entonces un intento read(2)desde la tubería verá el final del archivo ( read(2)devolverá 0).

Cerrar el final de la escritura es lo que sucede implícitamente al final de la echo ls >f, y como usted dice, en el otro caso, el descriptor de archivo se mantiene abierto.

ilkkachu
fuente
¡Parece un poco análogo a los recuentos de referencia en Java (y otros lenguajes OO)! Sin embargo, tiene sentido.
Fixee
2

Después de leer las dos respuestas de @Kusalananda y @ikkachu, creo que entiendo. En la ventana 1, el shell está esperando que algo abra el extremo de escritura de la tubería y luego lo cierre. Una vez que se abre el final de la escritura, el shell en la ventana-1 imprime un mensaje. Una vez que se cierra el final de escritura, el shell obtiene EOF y muere.

Por el lado de la ventana-2 tenemos las dos situaciones descritas en mi pregunta: en la primera situación con echo ls > f, no hay descriptor de archivo 3, por lo que hemos echodesove, y su stdiny stdouttener este aspecto:

0 --> tty
1 --> f

Luego echotermina y el shell cierra ambos descriptores. Dado que el descriptor de archivo 1 está cerrado y las referencias f, el final de escritura de festá cerrado y eso hace que un EOF a window-1.

En la segunda situación, corremos exec 3>fen nuestro shell, haciendo que el shell tome este entorno:

bash:
0 --> tty
1 --> tty
2 --> tty
3 --> f

Ahora ejecutamos echo ls >& 3y el shell asigna descriptores de archivo de la echosiguiente manera:

echo:
0 --> tty
1 --> f     # because 3 points to f
2 --> tty

Luego, el shell cierra los tres descriptores anteriores, incluidos f, pero faún tiene una referencia desde el propio shell. Esta es la diferencia importante. Cerrar el descriptor 3 con exec 3>&-cerraría la última referencia abierta y causaría un EOF a la ventana-1, como señaló @Kusalananda.

Fixee
fuente
Este es un buen ejemplo de por qué debería dejar solos los primeros tres descriptores de archivo a menos que haya una buena razón de diseño para modificarlos. Cuando usó el descriptor (1) que terminó siendo el descriptor de entrada (0) al otro shell, no solo cerró la tubería (y lo que estaba haciendo con ese flujo de datos en particular), sino que también cerró la entrada al segundo shell que hizo que terminara. Esto está bien, pero solo si lo estás haciendo a propósito. El uso de descriptores de archivo con números más altos evita efectos secundarios como este porque nada espera que estén en un estado particular o incluso definidos.
Joe
Para ser honesto, no estoy seguro de lo que estaba tratando de decir en ese comentario, simplemente lo eliminaré.
Stéphane Chazelas