Canaux nommés, descripteurs de fichiers et EOF

10

Deux fenêtres, même utilisateur, avec des invites bash. Dans le type de fenêtre 1:

$ mkfifo f; exec <f

Donc bash tente maintenant de lire à partir du descripteur de fichier 0, qui est mappé sur le canal nommé f. Dans le type de fenêtre 2:

$ echo ls > f

Maintenant, window-1 imprime un ls puis le shell meurt. Pourquoi?

Expérience suivante: ouvrez à nouveau la fenêtre-1 avec exec <f. Dans le type de fenêtre 2:

$ exec 3>f
$ echo ls >&3

Après la première ligne ci-dessus, la fenêtre-1 se réveille et imprime une invite. Pourquoi? Après la deuxième ligne ci-dessus, window-1 imprime la lssortie et le shell reste vivant. Pourquoi? En fait, maintenant dans la fenêtre-2, echo ls > fne ferme pas le shell de la fenêtre-1.

La réponse doit avoir à voir avec l' existence du descripteur de fichier 3 de la fenêtre 2 référençant le canal nommé?!

Fixee
la source
1
Après exec <f, bashon ne cherche pas à lire à partir f, il est d' abord tenté d' ouvrir il. Le open()ne reviendra pas tant qu'il n'y aura pas de processus faisant une autre ouverture en mode écriture dans le tuyau (à quel moment le tuyau sera instancié et le shell lira les entrées de celui-ci).
Stéphane Chazelas
Excellent point, @ StéphaneChazelas. Ce doit être la raison pour laquelle, une fois exec 3>fexécuté, le premier shell donne ensuite une invite. (Point mineur, vouliez-vous dire "en mode écriture " dans votre commentaire?)
Fixee
1
Oui désolé. Modifié maintenant juste avant le délai de 5 minutes
Stéphane Chazelas

Réponses:

12

Cela concerne la fermeture du descripteur de fichier.

Dans votre premier exemple, echoécrit dans son flux de sortie standard avec lequel le shell s'ouvre pour le connecter f, et lorsqu'il se termine, son descripteur est fermé (par le shell). À la réception, le shell, qui lit les entrées de son flux d'entrée standard (connecté à f), lit ls, s'exécute lspuis se termine en raison de la fin du fichier sur son entrée standard.

La condition de fin de fichier se produit car tous les rédacteurs du canal nommé (un seul dans cet exemple) ont fermé leur extrémité du canal.

Dans votre deuxième exemple, exec 3>fouvre le descripteur de fichier 3 pour l'écriture f, puis y echoécrit ls. C'est le shell qui a maintenant le descripteur de fichier ouvert, pas la echocommande. Le descripteur reste ouvert jusqu'à ce que vous le fassiez exec 3>&-. À la réception, le shell, qui lit les entrées de son flux d'entrée standard (connecté à f), lit ls, s'exécute lspuis attend plus d'entrée (car le flux est toujours ouvert).

Le flux reste ouvert car tous les rédacteurs (shell, via exec 3>fet echo) n'ont pas fermé leur extrémité du tube ( exec 3>fest toujours en vigueur).


J'ai écrit plus echohaut comme s'il s'agissait d'une commande externe. Il est très probablement intégré à la coque. L'effet est néanmoins le même.

Kusalananda
la source
6

Il n'y a pas grand-chose: quand il n'y a pas d'écrivain dans le tube, il semble fermé aux lecteurs, c'est-à-dire qu'il retourne EOF à la lecture et se bloque à l'ouverture.

Depuis la page de manuel Linux ( pipe(7), mais voir aussi fifo(7)):

Si tous les descripteurs de fichiers se référant à la fin d'écriture d'un canal ont été fermés, une tentative read(2)depuis le canal verra la fin du fichier ( read(2)retournera 0).

La fermeture de la fin d'écriture est ce qui se passe implicitement à la fin de la echo ls >f, et comme vous le dites, dans l'autre cas, le descripteur de fichier reste ouvert.

ilkkachu
la source
Semble un peu analogue aux comptes de référence en Java (et dans d'autres langages OO)! Cela a du sens cependant.
Fixee
2

Après avoir lu les deux réponses de @Kusalananda et @ikkachu, je pense que je comprends. Dans la fenêtre 1, le shell attend quelque chose pour ouvrir l'extrémité d'écriture du tuyau, puis la fermer. Une fois l'écriture terminée, le shell de la fenêtre-1 affiche une invite. Une fois la fin de l'écriture fermée, le shell obtient l'EOF et meurt.

Du côté de la fenêtre 2, nous avons les deux situations décrites dans ma question: dans la première situation avec echo ls > f, il n'y a pas de descripteur de fichier 3, nous avons donc le echofrai, et son stdinet stdoutressemble à ceci:

0 --> tty
1 --> f

Puis se echotermine et le shell ferme les deux descripteurs. Étant donné que le descripteur de fichier 1 est fermé et fait référence f, la fin d'écriture de fest fermée et cela provoque un EOF à window-1.

Dans la deuxième situation, nous exécutons exec 3>fdans notre shell, ce qui oblige le shell à prendre cet environnement:

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

Maintenant, nous exécutons echo ls >& 3et le shell alloue les descripteurs de fichiers echocomme suit:

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

Ensuite, le shell ferme les trois descripteurs ci-dessus, y compris f, mais a ftoujours une référence à lui à partir du shell lui-même. C'est la différence importante. La fermeture du descripteur 3 avec exec 3>&-fermerait la dernière référence ouverte et provoquerait un EOF à la fenêtre-1, comme l'a noté @Kusalananda.

Fixee
la source
C'est un bon exemple de la raison pour laquelle vous devez laisser les trois premiers descripteurs de fichier seuls, sauf s'il existe une bonne raison de les modifier. Lorsque vous avez utilisé le descripteur (1) qui a fini par être le descripteur d'entrée (0) de l'autre shell, vous avez non seulement fermé le canal (et ce que vous faisiez avec ce flux de données particulier), mais également fermé l'entrée au second shell qui l'a fait se terminer. C'est bien, mais seulement si vous le faites exprès. L'utilisation de descripteurs de fichiers plus élevés évite des effets secondaires comme celui-ci car rien ne s'attend à ce qu'ils soient dans un état particulier ou même définis.
Joe
Pour être honnête, je ne suis pas sûr de ce que j'essayais de dire dans ce commentaire, je vais juste le supprimer.
Stéphane Chazelas