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.
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
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
- The interrupt occurs: the device has data ready
- The interrupt request: the device sends an interrupt signal to the CPU
- The interrupt response: the CPU suspends its current work
- Saving the context: the current state of the CPU is saved
- Handling the interrupt: the interrupt service routine runs
- Restoring the context: the state of the CPU is restored
- 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
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
- The initialization phase: the CPU sets up the parameters of the DMA controller
- The transfer phase: the DMA controller takes direct control of the data
- 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
1. Performance Comparison
| Characteristic | Programmed I/O | Interrupt-Driven | DMA |
|---|---|---|---|
| CPU utilization | Low | Medium | High |
| Transfer efficiency | Low | Medium | High |
| Hardware complexity | Low | Medium | High |
| Software complexity | Low | Medium | High |
| Where it applies | Simple systems | Multitasking systems | High-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
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
| Term | Abbreviation | Explanation |
|---|---|---|
| Programmed IO | PIO | The CPU polls the device for its status |
| Interrupt Driven IO | - | The device interrupts the CPU once it is ready |
| Direct Memory Access | DMA | The device accesses memory on its own |
| Channel IO | - | A dedicated I/O processor controls the I/O |
| IO Processor | IOP | A 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 Request | IRQ | The 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 Table | IVT | The table that holds the interrupt vectors |
| Interrupt Priority | - | The priority rank of an interrupt |
| Interrupt Mask | - | The mechanism that disables selected interrupts |
| DMA Controller | DMAC | The 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 Word | CAW | Holds the address of the channel program |
| Channel Status Word | CSW | Holds the status of the channel |
