You are on page 1of 11

Synchronous I/O

• An I/O call that does not return until it has completed or some
specified time limit has elapsed
• Also called Blocking I/O
• Examples:
• TCP connect()
• read() from a disk file
• recv() from a network socket
• gethostbyname() for DNS resolution
Synchronous I/O
• Synchronous I/O suffices when your program is very simple and has
got nothing else to do
• Keeps the code easy to understand

• Makes the program very inefficient if it needs to handle multiple I/O


requests.
Asynchronous I/O
• Asynchronous I/O can be achieved using:
• Signal driven I/O: using the SIGIO signal, or
• The POSIX aio interface
• The signal driven I/O tells the program when the I/O operation may be
initiated whereas the aio interface tells the program when the I/O
operation has completed.
• Signal driven I/O is also blocking in that sense since the process is blocked while
the data is copied to the user buffers.
• The aio interface is truly non-blocking since it copies the data to the user buffers
and only then notifies the process.
• The POSIX aio interface is not widely supported on different platforms.
Multi-Threading
• Another technique to handle multiple I/O requests is to create one
thread (or process) for every request.
• Each thread can then execute a blocking I/O call without blocking the
entire process.
• The problem with this naïve approach is that it doesn’t scale well
when the number of I/O requests or network connections grows to
tens of thousands.
• A slightly better approach is to have a thread pool wherein threads
are reused across multiple I/O requests.
• This still suffers from the fundamental thread scaling problem.
I/O Multiplexing
• Capability of being notified when any of several I/O conditions are
ready.
• Some typical scenarios where I/O multiplexing is used:
• When a client is handling interactive input and a network socket
• When a server is handling a listening socket and connected sockets
• When a server handles multiple services and/or protocols
• When an application needs to handle several types of events such as network
I/O, disk I/O, timers, signals, etc.
• Normally achieved using the select() or poll() interface
Limitations of select/poll
• Select and poll have performance limitations as the number of
monitored file descriptors grows large
• Aside from events occurring on open file descriptors, an application
may be interested in other kinds of events such as:
• Delivery of a signal
• Changes to a file in the file system
• Exiting of a process
• None of these other event notification mechanisms are efficiently
implemented.
Limitations of select/poll
• Poll and select require an application to pass in the entire list of
descriptors to be monitored for every call.
• This requires two memory copies:
• On call, the kernel copies the requested fd list from user memory to kernel
memory.
• On return, the kernel copies the active fd list from kernel memory to user
memory.
• For large fd lists where only a few of the fds are active at any given
time, most of the memory copies are unnecessary.
Limitations of select/poll
• Multiple passes over the descriptor list required:
• Kernel makes one pass to look for pending events
• If poll/select sleep, then on wakeup, the kernel makes another pass to record the
active descriptors
• User application makes one pass to determine the active descriptors.
• Each pass is an O(N) activity, where N is the number of descriptors – this
doesn't scale well.
• Kernel needs to allocate memory for large lists.
• The central problem is that poll/select are stateless by design, i.e., the
kernel does not remember the set of monitored descriptors between calls.
So what to do?
• Different operating systems have provided different alternatives to
select()
• Linux provides epoll()
• FreeBSD provides kqueue()
• These interfaces provide O(1) performance for adding/removing a file
descriptor or for determining which file descriptor is ready.
• But how do we write a portable application that provides high
performance?
Libevent
• The Libevent C library provides an abstraction that wraps all the
available interfaces and uses the most efficient implementation for a
given platform.
• Supports file descriptors, signals and timers.

You might also like