Nieblokujące we / wy UNIX: O_NONBLOCK kontra FIONBIO

92

W każdym przykładzie i dyskusji, na którą natknąłem się w kontekście programowania gniazd BSD, wydaje się, że zalecanym sposobem ustawienia deskryptora pliku na nieblokujący tryb I / O jest użycie O_NONBLOCKflagi fcntl()np.

int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);

Od ponad dziesięciu lat zajmuję się programowaniem sieciowym w systemie UNIX i zawsze używałem tego FIONBIO ioctl()wywołania:

int opt = 1;
ioctl(fd, FIONBIO, &opt);

Nigdy tak naprawdę nie zastanawiałem się, dlaczego. Właśnie się tego nauczyłem.

Czy ktoś ma jakieś uwagi na temat możliwych zalet jednej lub drugiej? Wyobrażam sobie, że miejsce przenoszenia różni się nieco, ale nie wiem w jakim stopniu, ponieważ ioctl_list(2)nie dotyczy tego aspektu poszczególnych ioctlmetod.

Alex Balashov
źródło

Odpowiedzi:

135

Przed standaryzacją istniały ioctl(... FIONBIO... )i fcntl(... O_NDELAY... ), ale te zachowywały się niespójnie między systemami, a nawet w ramach tego samego systemu. Na przykład często FIONBIOdziałało na gniazdach, a na Solarisie odczyt bez danych zwracał 0 na terminalu lub potoku, lub -1 z errno EAGAIN na gnieździe. Jednak 0 jest niejednoznaczne, ponieważ jest również zwracane dla EOF.O_NDELAY ttys, z dużą ilością niespójności dla rzeczy takich jak rury, kolejki pięt i urządzenia. A jeśli nie wiesz, jaki masz deskryptor pliku, musisz ustawić oba, aby mieć pewność. Ale dodatkowo odczyt bez blokowania bez dostępnych danych był również wskazywany niespójnie; w zależności od systemu operacyjnego i typu deskryptora pliku, odczyt może zwrócić 0 lub -1 z errno EAGAIN lub -1 z errno EWOULDBLOCK. Nawet dzisiaj ustawienie FIONBIOlubO_NDELAY

POSIX rozwiązał ten problem, wprowadzając program O_NONBLOCK, który ma ustandaryzowane zachowanie w różnych systemach i typach deskryptorów plików. Ponieważ istniejące systemy zwykle chcą uniknąć jakichkolwiek zmian w zachowaniu, które mogłyby naruszyć wsteczną kompatybilność, POSIX zdefiniował nową flagę, zamiast narzucać określone zachowanie jednej z pozostałych. Niektóre systemy, takie jak Linux, traktują wszystkie 3 tak samo, a także definiują EAGAIN i EWOULDBLOCK na tę samą wartość, ale systemy, które chcą zachować inne starsze zachowanie w celu zapewnienia kompatybilności wstecznej, mogą to zrobić, gdy używane są starsze mechanizmy.

Nowe programy powinny używać fcntl(... O_NONBLOCK... ), zgodnie z normą POSIX.

mark4o
źródło
6
Zwykle używam do tego celu ioctl (), ponieważ włączenie trybu nieblokującego kosztuje mnie tylko jedno wywołanie systemowe, a nie dwa dla fcntl (). Ponadto interfejs API ioctlsocket () systemu Windows jest równoważny ioctl () na potrzeby tej funkcji.
Wez Furlong
nginx robi to, jeśli może, i oznacza to komentarzem "ioctl (FIONBIO) ustawia tryb nieblokujący z pojedynczym wywołaniem systemowym." Teraz jest accept2, która pozwala zaakceptować połączenie i przełączyć je w tryb nieblokujący w tym samym wywołaniu systemowym.
Eloff
6

Jak powiedział @Sean, fcntl()jest w dużej mierze znormalizowany, a zatem dostępny na różnych platformach. ioctl()Funkcja wyprzedza fcntl()w Uniksie, ale nie jest znormalizowany w ogóle. To, że ioctl()zadziałało dla Ciebie na wszystkich istotnych dla Ciebie platformach, jest szczęśliwe, ale nie jest gwarantowane. W szczególności nazwy użyte w drugim argumencie są tajemnicze i nie są wiarygodne na różnych platformach. W rzeczywistości są one często unikalne dla konkretnego sterownika urządzenia, do którego odnosi się deskryptor pliku. (Na przykład ioctl()wywołania używane do mapowanego bitowo urządzenia graficznego działającego na ICL Perq z systemem PNX (Perq Unix) sprzed dwudziestu lat nigdy nie zostały przetłumaczone na nic innego, na przykład).

Jonathan Leffler
źródło
5

Uważam, że fcntl()jest to funkcja POSIX. Gdzie ioctl()jest standardowa rzecz UNIX. Oto lista POSIX io . ioctl()jest bardzo specyficzną dla jądra / sterownika / systemu operacyjnego, ale jestem pewien, że to, czego używasz, działa na większości odmian Uniksa. niektóre inne ioctl()rzeczy mogą działać tylko na określonym systemie operacyjnym lub nawet na pewnych obrotach jego jądra.

EdH
źródło
Używałem FIONBIO na AIX, Solaris, Linux, * BSD i IRIX bez żadnych problemów. Ale tak, rozumiem, że na przykład nie będzie działać w systemie Windows - jest to interfejs niskiego poziomu do bardzo specyficznej implementacji jądra. Mimo to zastanawiam się, czy istnieją inne czynniki różnicujące.
Alex Balashov,