Nazwane potoki, deskryptory plików i EOF

10

Dwa okna, ten sam użytkownik, z monitami bash. W oknie typu 1:

$ mkfifo f; exec <f

Więc bash próbuje teraz odczytać z deskryptora pliku 0, który jest odwzorowany na nazwany potok f. W oknie typu 2:

$ echo ls > f

Teraz okno-1 wypisuje ls, a następnie powłoka umiera. Dlaczego?

Następny eksperyment: ponownie otwórz okno-1 za pomocą exec <f. W oknie typu 2:

$ exec 3>f
$ echo ls >&3

Po pierwszym wierszu powyżej okno-1 budzi się i wyświetla monit. Dlaczego? Po drugim wierszu powyżej okno-1 drukuje dane lswyjściowe, a powłoka pozostaje przy życiu. Dlaczego? W rzeczywistości, teraz w oknie-2, echo ls > fnie zamyka powłoki okna-1.

Odpowiedź musi mieć związek z istnieniem deskryptora pliku 3 z okna-2 odwołującego się do nazwanego potoku ?!

Fixee
źródło
1
Po exec <f, bashnie próbuje odczytać z f, to jest najpierw próbuje otworzyć go. open()Nie wróci, dopóki istnieje jakiś inny sposób robi się w trybie zapisu do rury (w którym momencie rura zostanie instancja, a powłoka będzie czytać wejście z nią).
Stéphane Chazelas,
Doskonały punkt, @ StéphaneChazelas. To musi być powód, dla którego po exec 3>furuchomieniu pierwsza powłoka wyświetla monit. (Drobny punkt, czy miałeś na myśli „w trybie pisania ” w swoim komentarzu?)
Fixee
1
tak, przepraszam. Edytowane teraz tuż przed 5-minutowym terminem
Stéphane Chazelas,

Odpowiedzi:

12

Ma to związek z zamknięciem deskryptora pliku.

W pierwszym przykładzie echozapisuje w swoim standardowym strumieniu wyjściowym, który powłoka otwiera, aby się z nią połączyć f, a po zakończeniu deskryptor jest zamykany (przez powłokę). Po stronie odbierającej powłoka, która odczytuje dane wejściowe ze standardowego strumienia wejściowego (podłączonego f), odczytuje ls, uruchamia się, lsa następnie kończy z powodu stanu końca pliku na swoim standardowym wejściu.

Występuje warunek końca pliku, ponieważ wszyscy autorzy nazwanego potoku (tylko jeden w tym przykładzie) zamknęli swój koniec potoku.

W drugim przykładzie exec 3>fotwiera deskryptor pliku 3 do zapisu f, a następnie echozapisuje lsw nim. To powłoka, która ma teraz otwarty deskryptor pliku, a nie echopolecenie. Deskryptor pozostaje otwarty, dopóki tego nie zrobisz exec 3>&-. Po stronie odbierającej powłoka, która odczytuje dane wejściowe ze swojego standardowego strumienia wejściowego (podłączonego do f), odczytuje ls, działa, lsa następnie czeka na więcej danych wejściowych (ponieważ strumień jest nadal otwarty).

Strumień pozostaje otwarty, ponieważ wszyscy piszący do niego (powłoka, poprzez exec 3>fi echo) nie zamknęli końca potoku ( exec 3>fnadal działa).


Pisałem o tym echowyżej, jakby to było polecenie zewnętrzne. Najprawdopodobniej jest wbudowany w powłokę. Efekt jest taki sam.

Kusalananda
źródło
6

Nie ma w tym nic wielkiego: gdy nie ma programów zapisujących do potoku, wygląda na zamknięty dla czytelników, tzn. Zwraca EOF podczas odczytu i blokuje po otwarciu.

Ze strony podręcznika systemu Linux ( pipe(7)ale zobacz także fifo(7)):

Jeśli wszystkie deskryptory plików odnoszące się do końca zapisu potoku zostały zamknięte, wówczas próba read(2)przejścia z potoku wyświetli koniec pliku ( read(2)zwróci 0).

Zamykanie końca zapisu jest tym, co niejawnie dzieje się na końcu echo ls >f, a jak mówisz, w innym przypadku deskryptor pliku pozostaje otwarty.

ilkkachu
źródło
Wydaje się to być analogiczne do liczby referencji w Javie (i innych językach OO)! Ma to jednak sens.
Fixee
2

Po przeczytaniu dwóch odpowiedzi z @Kusalananda i @ikkachu, myślę, że rozumiem. W oknie-1 powłoka czeka, aż coś otworzy koniec zapisu potoku, a następnie go zamknie. Po otwarciu końca zapisu powłoka w oknie-1 drukuje monit. Gdy koniec zapisu zostanie zamknięty, powłoka otrzymuje EOF i umiera.

Po stronie okna-2 mamy dwie sytuacje opisane w moim pytaniu: w pierwszej sytuacji z echo ls > f, nie ma deskryptora pliku 3, więc mamy echospawnowanie, i to stdini stdoutwygląda tak:

0 --> tty
1 --> f

Następnie echokończy się, a powłoka zamyka oba deskryptory. Ponieważ deskryptor pliku 1 jest zamknięty i zawiera odwołania f, koniec zapisu fjest zamknięty, co powoduje EOF dla okna-1.

W drugiej sytuacji działamy exec 3>fw naszej powłoce, powodując, że powłoka przyjmuje następujące środowisko:

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

Teraz uruchamiamy, echo ls >& 3a powłoka przydziela deskryptory plików echow następujący sposób:

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

Następnie powłoka zamyka trzy powyższe deskryptory, w tym f, ale fnadal ma do niej odniesienie z samej powłoki. To jest ważna różnica. exec 3>&-Jak zauważył @Kusalananda, zamknięcie deskryptora 3 za pomocą zamknęłoby ostatnie otwarte odwołanie i spowodowałoby EOF dla okna-1.

Fixee
źródło
Jest to dobry przykład tego, dlaczego pierwsze trzy deskryptory plików należy pozostawić w spokoju, chyba że istnieje uzasadniony powód do ich modyfikacji. Kiedy użyłeś deskryptora (1), który ostatecznie był deskryptorem wejściowym (0) dla drugiej powłoki, nie tylko zamknąłeś potok (i to, co robiłeś z tym konkretnym strumieniem danych), ale także zamknąłeś wejście do drugiego powłoka, która spowodowała jego zakończenie. To dobrze, ale tylko jeśli robisz to celowo. Używanie deskryptorów plików o wyższych numerach pozwala uniknąć takich skutków ubocznych, ponieważ nic nie oczekuje, że będą w jakimkolwiek określonym stanie, a nawet nie zostaną zdefiniowane.
Joe
Szczerze mówiąc, nie jestem pewien, co próbowałem powiedzieć w tym komentarzu, po prostu go usunę.
Stéphane Chazelas