I/O Control Methods

An I/O control method is the way the CPU exchanges data with an external device. Each method has its own profile in terms of efficiency, complexity, and the situations it suits. This lesson examines the three main methods in turn — programmed I/O, interrupt-driven I/O, and DMA — and then compares them directly.

Programmed I/O

1. The Basic Principle

Programmed I/O is the simplest of the three methods. The CPU repeatedly reads the device’s status register to find out whether the device is ready to transfer data, and moves the data itself when it is.

2. The Workflow

// The basic workflow of programmed I/O
typedef struct {
    int device_id;           // device ID
    int status_register;     // status register
    int data_register;       // data register
    int control_register;    // control register
} device_registers_t;

// A programmed read operation
int program_controlled_read(int device_id, void *buffer, int size) {
    device_registers_t *dev = get_device(device_id);
    int bytes_read = 0;

    while (bytes_read < size) {
        // Poll the device status
        while (!(dev->status_register & DEVICE_READY)) {
            // Wait for the device to become ready
        }

        // Read the data
        *((char*)buffer + bytes_read) = dev->data_register;
        bytes_read++;
    }

    return bytes_read;
}

// A programmed write operation
int program_controlled_write(int device_id, void *buffer, int size) {
    device_registers_t *dev = get_device(device_id);
    int bytes_written = 0;

    while (bytes_written < size) {
        // Poll the device status
        while (!(dev->status_register & DEVICE_READY)) {
            // Wait for the device to become ready
        }

        // Write the data
        dev->data_register = *((char*)buffer + bytes_written);
        bytes_written++;
    }

    return bytes_written;
}

3. Analysis of the Characteristics

Advantages

  • Simple to implement: both the hardware and the software are straightforward
  • Easy to understand: the control flow is direct and obvious
  • Highly reliable: the CPU controls every step of the process

Disadvantages

  • Low CPU utilization: the CPU spends most of its time waiting
  • Poor efficiency: it cannot get on with other work in the meantime
  • Poor responsiveness: it cannot react to other events promptly

4. When It Applies

  • Simple embedded systems
  • Situations with modest real-time requirements
  • Cases where device operations are infrequent

Interrupt-Driven I/O

1. The Basic Principle

Interrupt-driven I/O uses interrupts to make the exchange between CPU and device asynchronous. When the device is ready it signals the CPU, which suspends its current work and performs the I/O operation.

2. The Interrupt Handling Mechanism

The CPU finds the handler for an interrupt through a vector table, so each source of interrupts needs an entry that names its handler and states its priority.

// The interrupt vector table
typedef struct {
    void (*interrupt_handler)(void);  // interrupt handler
    int priority;                     // interrupt priority
    int enabled;                      // interrupt enable flag
} interrupt_vector_t;

// An example interrupt handler
void keyboard_interrupt_handler(void) {
    // Save the context
    save_context();

    // Read the keyboard data
    char key = read_keyboard_data();

    // Process the keyboard input
    process_keyboard_input(key);

    // Send the EOI (end of interrupt)
    send_eoi();

    // Restore the context
    restore_context();
}

// An interrupt-driven I/O operation
int interrupt_driven_read(int device_id, void *buffer, int size) {
    device_registers_t *dev = get_device(device_id);

    // Install the interrupt handler
    set_interrupt_handler(device_id, device_interrupt_handler);

    // Enable the interrupt
    enable_interrupt(device_id);

    // Start the I/O operation
    dev->control_register |= START_READ;

    // Wait for the interrupt to complete the operation
    wait_for_completion();

    return size;
}

3. The Interrupt Handling Flow

The Interrupt Response Sequence

  1. The interrupt occurs: the device has data ready
  2. The interrupt request: the device sends an interrupt signal to the CPU
  3. The interrupt response: the CPU suspends its current work
  4. Saving the context: the current state of the CPU is saved
  5. Handling the interrupt: the interrupt service routine runs
  6. Restoring the context: the state of the CPU is restored
  7. Resuming execution: the interrupted program continues

Interrupt Priority

Not every interrupt deserves the same attention: a timer tick should not delay the handling of a disk completion by very much. Priority levels and a mask register let the system order them.

// Interrupt priority levels
typedef enum {
    INT_PRIORITY_LOW = 0,    // low priority
    INT_PRIORITY_NORMAL,     // normal priority
    INT_PRIORITY_HIGH,       // high priority
    INT_PRIORITY_CRITICAL    // critical priority
} interrupt_priority_t;

// Interrupt priority management
typedef struct {
    interrupt_priority_t priority;
    int mask;                // interrupt mask bit
    void (*handler)(void);   // handler function
} interrupt_management_t;

4. Analysis of the Characteristics

Advantages

  • Higher CPU utilization: the CPU can do other work while it waits
  • Better responsiveness: device requests are answered promptly
  • Support for multiple devices: several devices can be managed at once

Disadvantages

  • Complex to implement: an interrupt handling mechanism is required
  • Considerable overhead: handling an interrupt costs a certain amount
  • Hard to debug: interrupt handlers are difficult to debug

5. When It Applies

  • Multitasking systems
  • Real-time systems
  • Situations that demand high responsiveness

Direct Memory Access (DMA)

1. The Basic Principle

DMA (Direct Memory Access) lets a device reach into memory without the CPU taking part in the transfer itself. The CPU is involved only at the start of a transfer and at the end of it.

2. The Structure of the DMA Controller

The DMA controller is the hardware that actually performs the transfer. It needs to be told where the data lives in memory, how much of it there is, and in which direction it should move.

// The structure of a DMA controller
typedef struct {
    int channel_id;          // channel ID
    void *memory_address;    // memory address
    int transfer_count;      // transfer count
    int control_register;    // control register
    int status_register;     // status register
    void (*start_transfer)(void);
    void (*stop_transfer)(void);
} dma_controller_t;

// DMA transfer control
typedef struct {
    dma_controller_t *dma;
    int device_id;
    void *buffer;
    int size;
    int direction;           // direction of the transfer
} dma_transfer_t;

// A DMA read operation
int dma_read(int device_id, void *buffer, int size) {
    dma_controller_t *dma = get_dma_controller();
    device_registers_t *dev = get_device(device_id);

    // Configure the DMA controller
    dma->memory_address = buffer;
    dma->transfer_count = size;
    dma->control_register = DMA_READ | DMA_ENABLE;

    // Configure the device
    dev->control_register = DEVICE_READ | DMA_MODE;

    // Start the DMA transfer
    dma->start_transfer();

    // Wait for the transfer to complete
    while (!(dma->status_register & DMA_COMPLETE)) {
        // Wait for the transfer to complete
    }

    return size;
}

// A DMA write operation
int dma_write(int device_id, void *buffer, int size) {
    dma_controller_t *dma = get_dma_controller();
    device_registers_t *dev = get_device(device_id);

    // Configure the DMA controller
    dma->memory_address = buffer;
    dma->transfer_count = size;
    dma->control_register = DMA_WRITE | DMA_ENABLE;

    // Configure the device
    dev->control_register = DEVICE_WRITE | DMA_MODE;

    // Start the DMA transfer
    dma->start_transfer();

    // Wait for the transfer to complete
    while (!(dma->status_register & DMA_COMPLETE)) {
        // Wait for the transfer to complete
    }

    return size;
}

3. The DMA Transfer Process

The Transfer Phases

  1. The initialization phase: the CPU sets up the parameters of the DMA controller
  2. The transfer phase: the DMA controller takes direct control of the data
  3. The completion phase: the DMA controller notifies the CPU that the transfer has finished

Bus Arbitration

Because the DMA controller and the CPU share the same bus, something has to decide who may use it at any moment. An arbiter grants the bus to one requester at a time and takes it back when the transfer is over.

// The bus arbitration mechanism
typedef struct {
    int bus_arbiter;         // the bus arbiter
    int current_owner;       // the current owner of the bus
    int request_queue[MAX_DEVICES];  // the request queue
    void (*grant_bus)(int device_id);
    void (*release_bus)(int device_id);
} bus_arbitration_t;

4. Analysis of the Characteristics

Advantages

  • Highest CPU utilization: the CPU is almost entirely out of the data path
  • High transfer efficiency: well suited to moving data in bulk
  • Good concurrency: the CPU and the I/O path genuinely run in parallel

Disadvantages

  • Complex hardware: a DMA controller is required
  • Higher cost: additional hardware has to be paid for
  • Hard to debug: DMA transfers are difficult to debug

5. When It Applies

  • Bulk data transfers
  • High-speed devices such as hard disks and network interfaces
  • Situations in which CPU utilization matters a great deal

Comparing the Three Methods

1. Performance Comparison

CharacteristicProgrammed I/OInterrupt-DrivenDMA
CPU utilizationLowMediumHigh
Transfer efficiencyLowMediumHigh
Hardware complexityLowMediumHigh
Software complexityLowMediumHigh
Where it appliesSimple systemsMultitasking systemsHigh-speed I/O

The pattern is consistent: every step up the ladder buys the CPU back time and costs extra hardware and extra software complexity. The interesting engineering decision is therefore not which method is best, but which one the situation can afford.

2. Choosing Between Them

When to Choose Programmed I/O

  • Simple embedded systems
  • Device operations that are infrequent
  • Situations with modest real-time requirements

When to Choose Interrupt-Driven I/O

  • Multitasking operating systems
  • Situations that call for a prompt response
  • Device operations of moderate frequency

When to Choose DMA

  • Bulk data transfers
  • High-speed device operations
  • Situations that place a high value on CPU utilization

Hybrid Control Methods

Real systems rarely commit to a single method. A hybrid approach chooses per request, so that a large transfer on a fast device uses DMA while a single keystroke uses an interrupt.

1. Layered Control

// The structure of hybrid I/O control
typedef struct {
    int control_method;      // control method
    int device_speed;        // device speed
    int data_size;           // size of the data
    void (*select_method)(int device_id, int data_size);
} hybrid_io_control_t;

// Choose the control method according to the situation
void select_io_method(int device_id, int data_size) {
    device_info_t *dev = get_device_info(device_id);

    if (data_size < SMALL_THRESHOLD) {
        // Use interrupt-driven I/O for a small amount of data
        use_interrupt_driven(device_id);
    } else if (dev->speed > HIGH_SPEED_THRESHOLD) {
        // Use DMA for a high-speed device
        use_dma(device_id);
    } else {
        // Use interrupt-driven I/O in every other case
        use_interrupt_driven(device_id);
    }
}

2. Adaptive Control

  • Select the control method dynamically from the device characteristics and the amount of data
  • Adjust the control strategy while the system is running
  • Optimize the performance of the system as a whole

Glossary

TermAbbreviationExplanation
Programmed IOPIOThe CPU polls the device for its status
Interrupt Driven IO-The device interrupts the CPU once it is ready
Direct Memory AccessDMAThe device accesses memory on its own
Channel IO-A dedicated I/O processor controls the I/O
IO ProcessorIOPA stand-alone processor dedicated to I/O
Polling-The CPU repeatedly checks the device status
Busy Waiting-The CPU spins in a loop while it waits for a device
Interrupt-A notification sent by a device to the CPU
Interrupt RequestIRQThe signal that raises an interrupt
Interrupt Response-The CPU acknowledging an interrupt
Interrupt Handling-The process of handling an interrupt
Interrupt Vector-The entry address of an interrupt handler
Interrupt Vector TableIVTThe table that holds the interrupt vectors
Interrupt Priority-The priority rank of an interrupt
Interrupt Mask-The mechanism that disables selected interrupts
DMA ControllerDMACThe hardware that controls a DMA transfer
DMA Request-A device asking the DMA controller for a transfer
DMA Response-The controller granting a DMA request
Cycle Stealing-The DMA controller borrowing CPU bus cycles
Bus Stealing-The DMA controller taking control of the bus
Burst Transfer-The DMA controller transferring a block back to back
Channel Command-A command executed by the channel
Channel Program-The program executed by the channel
Channel Address WordCAWHolds the address of the channel program
Channel Status WordCSWHolds the status of the channel

Found an issue with this content?

Tell us what could be improved.