Problem nie jest w prymitywach read/write, bo one nie wiedzą, że piszą do urządzeń to raz. Dwa to jest konsekwencja VFS, trzymania otwartego uchwytu w kontekście VFS io. Zjawisko, które powoduje opisywany problem jest bardziej skomplikiwany, nie jest wadą ale kkonsekwecja użycia wartswy API gdzie mamy read//write Ominięcie tego problemu to komunikacja zurządzeniem nie przez read/write ale bezpośrednio z driverem urządzenia (i tracąc wszystkie inne zalety użycia warstwy gdzie mamy read/write).
W sumie nie powinno to nic zmieniać. Spodziewałbym się, że po odpięciu urządzenia select() zwróci odczytywalność, a read() zwróci 0 (ale nie sprawdzałem).
Tak się składa, że mam teraz na tapecie program, który gada z ttyACM (moduł cdc_acm) blokującym I/O (naprzemiennie pisze do portu i czeka na odpowiedź). Po odpięciu kabelka blokujący read() zwrócił 0 (EOF), a późniejszy tcdrain (wywołujący ioctl TCSBRK) -1 (errno = EIO).
Dodatkowy test pokazał, że gdy read() zwróci EOF, to kolejny read() również zwraca EOF, ale kolejny write() zwraca -1 z errno = EIO. Kołacze mi się po głowie, że w przypadku socketów zachowanie read() było inne (gdy zwrócił EOF, to kolejny read() zwracał błąd), ale głowy za to uciąć nie dam -- może mi się coś przywidziało.
Nie wiem czy cokolwiek zmienia fakt, że urządzenie nie jest podłączone bezpośrednio, tylko przez "przejęcie" portu w VirtualBox (ten Linux chodzi w wirtualce na Windows 7). Niby nie powinien.
Zacząłem to jeszcze raz sprawdzać i na ubuntu 14 (kernel 4.4.0) mam tak:
write zwraca błąd i errno=5 (EIO, Input/output error) jeśli urządzenie zniknie, niezależnie czy używam trybu blokującego czy nie.
read w trybie blokującym czeka na dane, jak wypnę w trakcie czekania wtyczkę to przerywa czekanie zwracając 0, czego nie traktuję jako błąd. Kolejne wywoływania read zwracają cały czas 0
read w trybie nieblokującym zwraca mi błąd i errno=11 (EAGAIN, Resource temporarily unavailable) gdy wtyczka jest wpięta i nie ma danych do odbioru czyli zachwuje się prawidłowo. Ale za to zwraca 0 (brak błędu) jak wtyczkę wypnę.
Testy z read powtórzyłem też na ubuntu 16 z kernelem 4.4, zachowanie identyczne.
Problem polega na tym, że mam urządzenia z który tylko czytam dane (skanery, klawiatury) i takie zachowanie read jest delikatnie mówiąc irytujące.
Wartość 0 oznacza EOF ("koniec pliku", zamknięte połączenie, koniec strumienia danych). To coś innego niż brak danych (bo wtedy jak sam zauważasz blokujący read poczeka, a nieblokujący zwróci EAGAIN).
Hmm, jak dla mnie jest prawidłowe. Po prostu read() nie ma prawa zwrócić
0, jeżeli urządzenie jest podłączone i działa, ale nie ma danych. Wartość
0 oznacza, że kanał komunikacyjny został zamknięty i dane się skończyły (to nie to samo, co chwilowy brak danych, które mogą przyjść później, bo strumień jest otwarty; gdy read() zwróci 0, to dane już nie przyjdą).
Skoro write potrafi zwrócić kod 'Input/output error', to spodziewałbym się takiego samego kodu błędu po read. To nie jest chwilowy brak danych, który później się pojawią.
Taka już jest konwencja read(). Chwilowy brak danych jest sygnalizowany przez blokowanie lub EAGAIN, permanentny brak danych przez 0 (EOF). EOF oznacza "zakończ pracę, bo więcej już nie odczytasz". Czy jest to błąd, czy normalna sytuacja, to zależy od założeń.
Zakładam, że wywodzi się to stąd, że read() / write() oryginalnie służą do zapisu / odczytu plików. Gdy czytasz z pliku, to ten plik kiedyś się skończy i nie jest to błąd, podczas gdy nieudany zapis do pliku zawsze jest błędem. Przy strumieniach z urządzeń jest podobnie.
Po prostu odłączenie urządzenia jest traktowane jako koniec strumienia, a nie błąd I/O.
Przykład z plikiem chyba nie jest najlepszy, bo EOF "teraz" nie znaczy, że "za chwilę" się tam nic nie pojawi do dalszego czytania. Najprostszy przykład to logi. EOF w przypadku czytania logów to na pewno nie jest błąd, po którym nie masz innego wyjścia jak zamknięcie pliku.
Zazwyczaj jednak pliki czyta się od początku do końca, a po końcu zamyka. Sytuacja, w której program przewiduje, że plik jeszcze urośnie, jest nietypowa. Większość narzędzi Linuksowych standardowo tak się zachowuje. Jak chcesz przeczytać log "od początku do anulowania" (a nie "od początku do końca tego, co w nim aktualnie jest") to przecież nie używasz cat, tylko tail -f.
Zresztą taki "follow" rodzi problem, jeśli plik zostanie w międzyczasie przycięty, bo wskaźnik pliku nie przesuwa się do początku.
Inna sprawa, że nie da się usunąć pliku, który jest aktualnie otwarty (można usunąć dowiązanie, ale inode zostaje do momentu zamknięcia), więc takie zachowanie read() dla pliku ma sens. Czy ma dla urządzenia... widzę argumenty i za, i przeciw, i pewnie można się kłócić i dyskutować, jak powinno być, ale obecny stan jest taki, że read() zachowuje się tak, a nie inaczej... podejrzewam, że ktoś to przemyślał.
ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here.
All logos and trade names are the property of their respective owners.