UNIX nonblocking I/O: O_NONBLOCK vs. FIONBIO

121,051

Solution 1

Prior to standardization there was ioctl(...FIONBIO...) and fcntl(...O_NDELAY...), but these behaved inconsistently between systems, and even within the same system. For example, it was common for FIONBIO to work on sockets and O_NDELAY to work on ttys, with a lot of inconsistency for things like pipes, fifos, and devices. And if you didn't know what kind of file descriptor you had, you'd have to set both to be sure. But in addition, a non-blocking read with no data available was also indicated inconsistently; depending on the OS and the type of file descriptor the read may return 0, or -1 with errno EAGAIN, or -1 with errno EWOULDBLOCK. Even today, setting FIONBIO or O_NDELAY on Solaris causes a read with no data to return 0 on a tty or pipe, or -1 with errno EAGAIN on a socket. However 0 is ambiguous since it is also returned for EOF.

POSIX addressed this with the introduction of O_NONBLOCK, which has standardized behavior across different systems and file descriptor types. Because existing systems usually want to avoid any changes to behavior which might break backward compatibility, POSIX defined a new flag rather than mandating specific behavior for one of the others. Some systems like Linux treat all 3 the same, and also define EAGAIN and EWOULDBLOCK to the same value, but systems wishing to maintain some other legacy behavior for backward compatibility can do so when the older mechanisms are used.

New programs should use fcntl(...O_NONBLOCK...), as standardized by POSIX.

Solution 2

I believe fcntl() is a POSIX function. Where as ioctl() is a standard UNIX thing. Here is a list of POSIX io. ioctl() is a very kernel/driver/OS specific thing, but i am sure what you use works on most flavors of Unix. some other ioctl() stuff might only work on certain OS or even certain revs of it's kernel.

Solution 3

As @Sean said, fcntl() is largely standardized, and therefore available across platforms. The ioctl() function predates fcntl() in Unix, but is not standardized at all. That the ioctl() worked for you across all the platforms of relevance to you is fortunate, but not guaranteed. In particular, the names used for the second argument are arcane and not reliable across platforms. Indeed, they are often unique to the particular device driver that the file descriptor references. (The ioctl() calls used for a bit-mapped graphics device running on an ICL Perq running PNX (Perq Unix) of twenty years ago never translated to anything else anywhere else, for example.)

Share:
121,051
Alex Balashov
Author by

Alex Balashov

Updated on August 24, 2022

Comments

  • Alex Balashov
    Alex Balashov almost 2 years

    In every example and discussion I run across in the context of BSD socket programming, it seems that the recommended way to set a file descriptor to nonblocking I/O mode is using the O_NONBLOCK flag to fcntl(), e.g.

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

    I've been doing network programming in UNIX for over ten years, and have always used the FIONBIO ioctl() call to do this:

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

    Never really gave much thought to why. Just learned it that way.

    Does anyone have any commentary on the possible respective merits of one or the other? I imagine the portability locus differs somewhat, but do not know to what extent as ioctl_list(2) doesn't speak to that aspect of individual ioctl methods.

  • Alex Balashov
    Alex Balashov almost 15 years
    I've used FIONBIO on AIX, Solaris, Linux, *BSD, and IRIX with no problems. But yeah, I understand that it's not going to work on Windows, for example--it's a low-level interface to a very specific kernel implementation. Still, I wonder if there are any other differentiating factors.
  • Wez Furlong
    Wez Furlong over 11 years
    I tend to use ioctl() for this because it costs me only one syscall to enable non-blocking mode rather than two for fcntl(). In addition, the Windows ioctlsocket() API is equivalent to ioctl() for the purposes of this functionality.
  • Eloff
    Eloff over 10 years
    nginx does this if it can, and marks it with a comment "ioctl(FIONBIO) sets a non-blocking mode with the single syscall." Now there is accept2 which lets you accept a connection and put it into non-blocking mode in the same syscall.