Polling, Interrupts, DMA, Synchronous, Asynchronous I/O Definitions

From: Randall Hyde (randyhyde_at_earthlink.net)
Date: 07/14/04

  • Next message: Auric__: "Re: [OT] run32.dll"
    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


  • Next message: Auric__: "Re: [OT] run32.dll"

    Relevant Pages