UNIX nonblocking I / O: O_NONBLOCK vs. FIONBIO

92

Dalam setiap contoh dan diskusi yang saya temui dalam konteks pemrograman soket BSD, tampaknya cara yang disarankan untuk mengatur deskriptor file ke mode I / O nonblocking adalah menggunakan O_NONBLOCKflag fcntl(), misalnya

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

Saya telah melakukan pemrograman jaringan di UNIX selama lebih dari sepuluh tahun, dan selalu menggunakan FIONBIO ioctl()panggilan untuk melakukan ini:

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

Tidak pernah benar-benar memikirkan mengapa. Baru saja mempelajarinya seperti itu.

Adakah yang punya komentar tentang kemungkinan manfaat masing-masing dari satu atau yang lain? Saya membayangkan lokus portabilitas agak berbeda, tetapi tidak tahu sejauh mana ioctl_list(2)tidak berbicara dengan aspek ioctlmetode individu .

Alex Balashov
sumber

Jawaban:

135

Sebelum standarisasi ada ioctl(... FIONBIO... )dan fcntl(... O_NDELAY... ), tapi ini berperilaku tidak konsisten antara sistem, dan bahkan dalam sistem yang sama. Misalnya, FIONBIObekerja pada soket dan O_NDELAYtty merupakan hal yang umum , dengan banyak ketidakkonsistenan untuk hal-hal seperti pipa, fifos, dan perangkat. Dan jika Anda tidak tahu jenis deskriptor file yang Anda miliki, Anda harus menyetel keduanya untuk memastikan. Namun sebagai tambahan, pembacaan non-pemblokiran tanpa data tersedia juga diindikasikan secara tidak konsisten; tergantung pada OS dan jenis deskriptor file, pembacaan dapat mengembalikan 0, atau -1 dengan errno EAGAIN, atau -1 dengan errno EWOULDBLOCK. Bahkan hari ini, pengaturan FIONBIOatauO_NDELAYpada Solaris menyebabkan pembacaan tanpa data mengembalikan 0 pada tty atau pipa, atau -1 dengan errno EAGAIN pada soket. Namun 0 adalah ambigu karena juga dikembalikan untuk EOF.

POSIX membahas ini dengan pengenalan O_NONBLOCK, yang memiliki perilaku standar di berbagai sistem dan jenis deskriptor file. Karena sistem yang ada biasanya ingin menghindari perubahan apa pun pada perilaku yang mungkin merusak kompatibilitas ke belakang, POSIX mendefinisikan sebuah flag baru daripada mengamanatkan perilaku tertentu untuk salah satu dari yang lain. Beberapa sistem seperti Linux memperlakukan ketiganya dengan sama, dan juga mendefinisikan EAGAIN dan EWOULDBLOCK ke nilai yang sama, tetapi sistem yang ingin mempertahankan beberapa perilaku lama lainnya untuk kompatibilitas ke belakang dapat melakukannya ketika mekanisme yang lebih lama digunakan.

Program baru harus menggunakan fcntl(... O_NONBLOCK... ), seperti yang distandarisasi oleh POSIX.

mark4o
sumber
6
Saya cenderung menggunakan ioctl () untuk ini karena biayanya hanya satu syscall untuk mengaktifkan mode non-pemblokiran daripada dua untuk fcntl (). Selain itu, Windows ioctlsocket () API setara dengan ioctl () untuk keperluan fungsionalitas ini.
Wez Furlong
nginx melakukan ini jika bisa, dan menandainya dengan komentar "ioctl (FIONBIO) menyetel mode non-pemblokiran dengan syscall tunggal." Sekarang ada accept2 yang memungkinkan Anda menerima koneksi dan memasukkannya ke mode non-pemblokiran di syscall yang sama.
Eloff
6

Seperti yang dikatakan @Sean, fcntl()sebagian besar terstandarisasi, dan oleh karena itu tersedia di seluruh platform. Ituioctl() Fungsi mendahului fcntl()di Unix, tetapi tidak standar sama sekali. Untunglah ioctl()bekerja untuk Anda di semua platform yang relevan dengan Anda, tetapi tidak dijamin. Secara khusus, nama yang digunakan untuk argumen kedua bersifat rahasia dan tidak dapat diandalkan di seluruh platform. Memang, mereka sering kali unik untuk driver perangkat tertentu yang dirujuk oleh deskriptor file. ( ioctl()Panggilan yang digunakan untuk perangkat grafis yang dipetakan bit yang berjalan pada ICL Perq yang menjalankan PNX (Perq Unix) dua puluh tahun yang lalu tidak pernah diterjemahkan ke hal lain di mana pun, misalnya.)

Jonathan Leffler
sumber
6

Saya percaya itu fcntl()adalah fungsi POSIX. Sedangkan ioctl()adalah hal UNIX standar. Berikut adalah daftar POSIX io . ioctl()adalah hal yang sangat spesifik untuk kernel / driver / OS, tetapi saya yakin apa yang Anda gunakan berfungsi pada sebagian besar varian Unix. beberapa ioctl()hal lain mungkin hanya bekerja pada OS tertentu atau bahkan revs tertentu dari kernelnya.

EdH
sumber
Saya telah menggunakan FIONBIO di AIX, Solaris, Linux, * BSD, dan IRIX tanpa masalah. Tapi ya, saya mengerti bahwa ini tidak akan berfungsi di Windows, misalnya - ini adalah antarmuka tingkat rendah untuk implementasi kernel yang sangat spesifik. Namun, saya bertanya-tanya apakah ada faktor pembeda lainnya.
Alex Balashov