Polling, Interrupts, DMA, Synchronous, Asynchronous I/O Definitions
From: Randall Hyde (randyhyde_at_earthlink.net)
Date: 07/14/04
- Previous message: Beth: "Re: Why RosAsm Breaks on a large number of symbols"
- Next in thread: Frank Kotler: "Re: Polling, Interrupts, DMA, Synchronous, Asynchronous I/O Definitions"
- Reply: Frank Kotler: "Re: Polling, Interrupts, DMA, Synchronous, Asynchronous I/O Definitions"
- Reply: f0dder: "Re: Polling, Interrupts, DMA, Synchronous, Asynchronous I/O Definitions"
- Maybe reply: Betov: "Re: Polling, Interrupts, DMA, Synchronous, Asynchronous I/O Definitions"
- Maybe reply: Randall Hyde: "Re: Polling, Interrupts, DMA, Synchronous, Asynchronous I/O Definitions"
- Maybe reply: f0dder: "Re: Polling, Interrupts, DMA, Synchronous, Asynchronous I/O Definitions"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Date: Wed, 14 Jul 2004 04:27:03 GMT
Elsewhere on this newsgroup, there seems to be considerable
confusion about the meaning of the terms "polling" and "asynchronous
operation." Largely, people are posting based on their limited
experiences with these terms in their own personal application space.
Let's take a look at some definitions of these terms as they are
found in traditional OS, machine organization, and computer
architecture textbooks.
Polling vs. Interrupts vs. DMA
I/O operations are generally broken up into three categories,
based upon how an application determines that an input
or output operation may proceed: polling, interrupts, or DMA.
>From Patterson and Hennessy's "Computer Organization & Design:
The Hardware/Software Interface" we read the following:
"The process of periodically checking status bits to see if it is
time for the next I/O operation, as in the previous example, is
called polling. .... The disadvantage of polling is that it can waste
a lot of processor time because processors are so much faster
than I/O devices."
The definition above, directly applies to code like this:
DoSomethingWithData:
Wait4Device:
in( dx, al );
test( 1, al );
jnz Wait4Device;
<< Do something with the Data>>
jmp DoSomethingWithData;
The alternative to polling, while still using programmed I/O,
is "interrupt-driven I/O." In an interrupt-driven system, the
application requests the start of an I/O operation and then
either suspends or does other work until the I/O operation
is complete and the application can use the data available
at an input port (or write more data to an output port).
The *fundamental* difference between the two lies in the
statement "...it can waste a lot of processor time..."
Polling is inherently inefficient because it uses CPU cycles
to see if data is available (or it's okay to write data to a port),
even if that condition does not exist.
Some people around here feel that for polling to take place,
the polling loop must be consuming all the CPU cycles.
This is a fundamental misconception that ignores the real-world
realities of multitasking systems. The loop above, for example,
in no way consumes all the CPU cycles in a modern operating
system - it gets preempted and other processes get their
time slice, just as happens with any normal application. The
important thing to note is that while the loop above is *not*
suspended, it burns CPU cycles until data is available.
Now let's muddy the water a little bit. Suppose you're
running on a protected mode operating system that virtualizes
certain hardware devices. That is, when you execute the "in"
instruction above, this generates an exception and the OS
takes over. The OS decodes the port request and then simulates
the in request (perhaps by actually reading the port). Then the
OS returns control to the application, loading the AL register
with the simulated port data.
Is this still polling? Of course it is. When the OS returns control
back to the application, it still executes that same loop testing
bit zero to determine if it can proceed; if not, it loops back and
tries again. Note that this is true *even* if the application is
blocked until some data is available. For example, the OS may
have an internal copy of the data and might not return control
to the application until that data changes since the last time the
application read that data. The fact that the application is
blocked does not change the fact that polling occurs. If the
data at the input port changes, but bit zero doesn't change
in a satisfactory manner to the application, the app continues
to loop back, wasting CPU cycles. The fact that this wastes
*fewer* cycles than it would if it accessed the port directly
is irrelevant. It's still polling. It's still wasting cycles.
Is it possible that this is *not* polling? Yes, it is. If the OS
is smart enough to know that the application is going to test
bit zero for a zero or one and *only* returns when bit zero
contains an appropriate value to exit the loop, then this is
not polling. Why the difference? Because the application
regains control *only* when the approprate data is available
(that is, a port value that has bit #0 clear, which immediately
exits the polling loop). Technically, the polling loop still wastes
a few CPU cycles testing that bit and not looping back, but
will ignore this bit of pedantic nit-picking.
The alternative to polling (while still doing programmed I/O)
is "interrupt-driven I/O". This type of I/O generally takes
one of two forms:
1. A separate "interrupt service routine" activates whenever
the data is available.
2. An application makes an OS call and blocks until an
interrupt comes along.
This latter form usually looks like this:
DoSomethingWithData:
<< Code to request I/O operation>>
APICallThatBlocksUntilInterrupt();
<< Do Something With Data Received >>
jmp DoSomethingWithData;
The important difference between the polling loop and the
interrupt-driven I/O loop is that the polling loop executes
whenever there isn't data of interest available. The interrupt
loop executes *only* when data of interest is available.
Now consider the following code:
InterruptServiceRoutine:
<< Get data and move to a shared memory location >>
mov( 1, DataAvailable );
iret();
MainThreadLoop:
<< Tell I/O device we want data >>
Wait4Data:
test( 1, DataAvailable );
jnz Wait4Data;
<<Do Something With Data >>
jmp MainThreadLoop;
No question this is using interrupt driven I/O. There is an interrupt
service routine that is activated in response to some hardware
device requesting service. However, it should *also* be obvious
that this application using polling (with all the attendant disadvantages).
The fact that interrupts are involved does *not* mean we're not polling.
It should be fairly clear that the Wait4Data loop is burning up CPU
cycles left and right waiting for the data to come along. All that is
happening here is that the interrupt service routine is *simulating*
a hardware port, not unlike the OS virtualizing the hardware port.
Yes, interrupts are taking place. Interrupt driven I/O is occuring.
But the application is *still* polling the statis bit.
Going back to the original definition of polling in Patterson and
Hennessy, we read "The process of PERIODICALLY checking...."
"Periodically" does not imply "as rapidly as possible. The following
is a perfectly reasonable polling loop:
DoSomethingWithData:
Wait4Device:
call DoSomeTimeConsumingOperation;
in( dx, al );
test( 1, al );
jnz Wait4Device;
<< Do something with the Data>>
jmp DoSomethingWithData;
Regardless of how long the subroutine call takes, this
code still periodically checks the status bit. Now consider
the following modification:
DoSomethingWithData:
Wait4Device:
call ReadPortDXIntoAL;
test( 1, al );
jnz Wait4Device;
<< Do something with the Data>>
jmp DoSomethingWithData;
The fact that we've moved placing the port's data in AL
into the function does not change the fact that we're still
polling. The loop continues to burn CPU cycles until bit
zero in AL contains a zero. That is, it's polling that port.
This remains true even if ReadPortDXIntoAL does
something like the following:
ReadPortDXIntoAL:
<< Block until the data at the port changes
since the last time we read it>>
<< Move the new port data into AL>>
return
This code blocks. You might think that it's not
polling as a result. Not true. Again, the subtle
difference between polling and not polling has
to do with the way the system unblocks the
application - if the system were to unblock
*only* when bit zero of the port contains
a zero, then it wouldn't be polling because
we're not wasting CPU cycles waiting for
the particular status we're interested in.
However, if all we're doing is simulating the
port (albeit more slowly), and we're returning
any old data, that the main app still has to test
and loop back on, then we're polling.
Now, it's time to introduce GetMessage into this
discussion. The argument others have made
(erroneously, I might add) is that because GetMessage
only returns when there is a message available, leaving
the app blocked until this occurs, means that we're
*not* polling. And if the app is waiting for *any*
message, this is true. That would be like the loops
above wanting *any* data at the port, not simply
waiting for bit zero to come back clear.
However, if a program calls GetMessage and looks
for a *specific* message, throwing all others out
(or passing them on to Windows for processing), this
*is* polling, for the same reasons as above -- we're
burning up CPU cycles testing for a condition that
may or may not currently exist (i.e., the receipt of
a desired message). In a *non-polling* system, the
application would *only* be called when a desired
message or desired set of messages is available
to the application.
The confusing people seem to be having with this
scenario is that they think that the fact that GetMessage
blocks until *any* message is available means that
polling isn't taking place because the app was blocked.
As we saw in the "virtualized I/O port" example, however,
it's quite possible for the application to be blocked while
waiting for an I/O operation to complete.
I've yet to read a single definition of polled
or interrupt-driven I/O that requires or forbids blocking.
Polling simply means that the application periodically
(and *synchronously*) tests for the availability of I/O,
interrupt-driven I/O means that the application does
not explicitly test for the presence of data - the hardware
or other system software somehow notifies the app
when the data is available.
As I've mentioned synchronous and asynchronous
operations, perhaps it's time to define those, as well.
"Synchronous" simply means "synchronized to something".
In the case of I/O operations, synchronous generally means
that when an I/O request is made, the application does not
proceed until the I/O operation complete.
"Asynchronous" means "not synchronized to anything."
With respect to I/O operations, an asynchronous I/O
request means that once the request is made the app
continues execution even if the I/O operation is not
complete. Somehow the system must notify the app
when the I/O is complete.
Polling can be either synchronous or asynchronous.
The polling loops given at the beginning of this article
are a good example of synchronous operation - the
app waits in the polling loop until the status bit says
that it is okay to proceed (the polling loop waits
until the system says "data is ready" and then drops
into the "do something with data" section of the code
when this is the case). Polling can also be asynchronous,
in the sense that you can execute lots of other unrelated
code and periodically check the status bits at various
points to see if the data is available. This is asynchronous
because you *aren't* waiting for the data to become
available before proceeding with other parts of the
application.
Interrupt-driven I/O can also be synchronous or
asynchronous. If you have a separate interrupt
service routine, it handles asynchronous events when
they occur. If your app blocks until an interrupt occurs,
that's a synchronous implementation of interrupt-driven I/O.
GetMessage is *clearly* a synchronous form of I/O.
It blocks until a new message is available.
PeekMessage is *clearly* an asynchronous form of I/O.
It immediately returns whether or not there is a message
available.
No doubt, based on the arguments in other threads, some
people are going to continue to argue that all calls to
GetMessage are "interrupt-driven" rather than "polling."
Unfortunately, such opinions are *not* supported by
definitions found in common architecture, OS, and
machine organization text books.
Cheers,
Randy Hyde
- Previous message: Beth: "Re: Why RosAsm Breaks on a large number of symbols"
- Next in thread: Frank Kotler: "Re: Polling, Interrupts, DMA, Synchronous, Asynchronous I/O Definitions"
- Reply: Frank Kotler: "Re: Polling, Interrupts, DMA, Synchronous, Asynchronous I/O Definitions"
- Reply: f0dder: "Re: Polling, Interrupts, DMA, Synchronous, Asynchronous I/O Definitions"
- Maybe reply: Betov: "Re: Polling, Interrupts, DMA, Synchronous, Asynchronous I/O Definitions"
- Maybe reply: Randall Hyde: "Re: Polling, Interrupts, DMA, Synchronous, Asynchronous I/O Definitions"
- Maybe reply: f0dder: "Re: Polling, Interrupts, DMA, Synchronous, Asynchronous I/O Definitions"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Relevant Pages
|