This does not mean that the only operations on an object are reading and writing: ioctl() and similar interfaces allow for object-specific operations (like controlling tty characteristics), directory file descriptors can be used to alter path look-ups (with a growing number of *at() system call variants like openat()[6]) or to change the working directory to the one represented by the file descriptor,[7] in both cases preventing race conditions and being faster than the alternative of looking up the entire path.
[8] Socket file descriptors require configuration (setting the remote address and connecting) after creation before being used for I/O.
This approach allows management of objects used by a program in a standardised manner, just like any other file — after binding to an address privileges may be dropped, the server socket may be distributed among many processes by fork()ing (respectively closed in subprocesses that should not have access), or the individual connections' sockets may be given as standard input/output to specialised handlers for those connections, as in the super-server/CGI/inetd paradigms.
Many interfaces present in early Unixes that do not use file descriptors became duplicated in later designs: the alarm()/setitimer() system calls schedule the delivery of a signal after the specified time elapses; this timer is inherited by children, and persists after exec().
Both interfaces always deliver their completions asynchronously, and cannot be poll()ed/select()ed, making their integration into a complex event loop more difficult.
For block devices (hard disks and tape drives), due to their size, this meant unique semantics: they were block-addressed (see [9]), and programs needed to be written specifically to work correctly with them.
Modern systems contain high-performance I/O event notification facilities — kqueue (BSD derivatives), epoll (Linux), IOCP (Windows NT, Solaris), /dev/poll (Solaris) — the control object is generally created (kqueue(), epoll_create()) and configured (kevent(), epoll_ctl()) with dedicated system calls.
Under Linux, the equivalent mechanism is provided by procfs under the /proc/sys tree: the respective operations can be done with find /proc/sys/grep -r ^ /proc/sys, cat /proc/sys/net/ipv4/ip_forward, and echo 1 > /proc/sys/net/ipv4/ip_forward.
For convenience or standards conformance, dedicated inspection tools (like ps and sysctl) may still be provided, using these filesystems as data sources/sinks.