You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
A processor on its own is useless: to be of any value it must take input from the outside world, produce output, and read and write storage. The difficulty is that peripherals are wildly slower than the CPU and speak many different electrical languages, so the machine needs a disciplined way to connect them and to move data to and from them without wasting processor time. This lesson classifies peripherals into input, output and storage devices; explains how a peripheral physically connects through a device controller and its registers; sets out the three techniques for transferring data — polling, interrupt-driven I/O and direct memory access (DMA); and shows precisely how buffers and interrupts allow a fast CPU and a slow device to cooperate. By the end you should be able to justify a transfer technique for a given device and explain the buffer/interrupt mechanism step by step.
This lesson addresses the AQA A-Level Computer Science (7517) specification within §4.7 Fundamentals of computer organisation and architecture. It covers the classification of hardware as input, output and storage devices (the device side of §4.7.1) and the way the processor and operating system communicate with peripherals — device controllers, the role of interrupts and buffers in input/output, and direct memory access — which sits alongside the processor material of §4.7.3/§4.7.4.
It links forwards to the secondary storage lesson (storage devices in detail) and to operating-system concepts of interrupt handling, and backwards to §4.7.3 the FDE cycle, since the interrupt check happens at the end of each cycle.
Every peripheral can be placed in one of three categories by the direction of data flow relative to the computer:
| Category | Data direction | Purpose | Examples |
|---|---|---|---|
| Input device | Into the computer | Convert real-world signals into data the CPU can process | Keyboard, mouse, microphone, scanner, sensor, touchscreen (input part) |
| Output device | Out of the computer | Convert data into a form humans or the environment can use | Monitor, printer, speaker, actuator, LED display |
| Storage device | Both (read and write) | Hold data and programs non-volatilely for later use | HDD, SSD, optical drive, USB flash drive |
Two subtleties examiners test. First, some devices are both input and output — a touchscreen senses touches (input) and displays an image (output); a network interface both receives and transmits. Second, storage devices are peripherals too: a hard drive connects through a controller and uses the same I/O techniques as any other device, which is why this lesson and the secondary-storage lesson are tightly linked.
A further classification worth knowing is by how data is represented at the boundary. Many real-world quantities are analogue (continuously varying — temperature, sound pressure, light level), but a digital computer works only in discrete binary. So input devices that sense the physical world usually rely on an analogue-to-digital converter (ADC) to turn a continuous signal into numbers, and output devices that act on the world often need a digital-to-analogue converter (DAC):
This matters in embedded and control systems, where the computer's whole job is to read sensors (input), make decisions, and drive actuators (output) in a continuous loop — a washing machine reads a water-level sensor and drives a pump and motor, a car engine controller reads many sensors and adjusts fuel injection. Recognising that sensors are inputs and actuators are outputs, and that ADCs/DACs bridge the analogue-digital boundary, is exactly the kind of precise classification a question rewards.
A peripheral never plugs straight into the CPU's internal wiring. It connects through an I/O port managed by a device controller (also called an I/O controller). The controller:
flowchart LR
CPU["CPU"] <-->|"system bus<br/>(address + data + control)"| DC["Device Controller<br/>status / control / data registers"]
DC <-->|"device-specific signals"| PD["Peripheral Device"]
The CPU therefore talks to a standardised controller, not to the messy specifics of each device. A device driver (software, below) tells the OS how to drive a particular controller. This layering — driver → controller registers → device — is what lets one operating system support thousands of different peripherals.
A typical interaction with the three registers makes the roles concrete. To send a character to a printer, the driver might: write the byte into the data register; write a "start print" command into the control register; then watch the status register until it shows the device is no longer busy. The status register is the CPU's window onto the device's condition (ready, busy, out of paper, error); the control register is the CPU's set of levers for commanding it; and the data register is the actual letterbox through which each byte passes. Whether the CPU watches the status register by polling or is told by an interrupt is precisely the choice the next section examines.
A buffer is a region of memory (often inside the controller, sometimes in main memory) that temporarily holds data in transit between the CPU and a slow device. Buffers exist because the CPU and a peripheral run at hugely different speeds: a printer might accept a few kilobytes per second while the CPU can supply data millions of times faster.
How a buffer helps, using printing as the example:
This decoupling means the fast CPU is not held hostage to the slow device's pace. The combination of buffering plus interrupts is the standard mechanism for efficient I/O: the buffer smooths the speed mismatch, and the interrupt signals when the buffer needs refilling or has new data ready.
With a single buffer there is still a moment of waiting. While the device is draining the buffer, the CPU cannot refill it (it would overwrite data not yet sent); and once the buffer is empty, the device must wait for the CPU to refill it before it can continue. So although a single buffer absorbs the average speed difference, both sides can still stall around the hand-over.
Double buffering removes most of that stall by using two buffers that swap roles. The principle:
flowchart LR
CPU["CPU<br/>(producer)"] -->|"fills"| BA["Buffer A"]
CPU -->|"fills"| BB["Buffer B"]
BA -->|"drains"| DEV["Slow device<br/>(consumer)"]
BB -->|"drains"| DEV
Because filling and draining now overlap continuously, neither side waits for the other except when one is genuinely faster overall. This is exactly how smooth video playback, audio streaming and print spooling keep a slow output device fed without stalling the CPU. The cost is simply the extra memory for the second buffer.
Exam Tip: A frequent question is "explain how a buffer and an interrupt are used together in I/O." A full answer states that the buffer holds data temporarily to absorb the speed difference, the CPU fills/empties it in a burst then does other work, and the device raises an interrupt when it needs servicing again. Name both the buffer's purpose and the interrupt's trigger. If asked about double buffering, add that one buffer is filled while the other is drained, so neither side stalls.
The central problem all three techniques solve is the same: the CPU must move data to or from a device that is far slower and operates independently, without wasting the processor's enormous speed. The three approaches sit on a spectrum of how much they involve the CPU — from "the CPU does everything and waits" (polling), through "the CPU is told only when needed" (interrupts), to "dedicated hardware does the bulk transfer and tells the CPU once at the end" (DMA). Each is the right answer for a different kind of device, so a good engineer chooses per device rather than applying one technique everywhere.
The CPU repeatedly reads the controller's status register in a loop until the device signals "ready", then transfers a datum via the data register.
| Step | Action |
|---|---|
| 1 | CPU issues a request to the device controller. |
| 2 | CPU repeatedly reads the status register in a busy-wait loop. |
| 3 | When the status register shows "ready", the CPU reads or writes via the data register. |
Advantages: simple; needs no extra hardware. Disadvantages: wasteful — the CPU is trapped in a busy-wait loop and can do no useful work; especially bad when the device is far slower than the CPU (almost always).
# Polling: the CPU is stuck here until the device is ready
REPEAT
status ← read(STATUS_REGISTER)
UNTIL status = READY
data ← read(DATA_REGISTER) # finally transfer one datum
Instead of polling, the device interrupts the CPU when it is ready, freeing the CPU to do other work meanwhile.
| Step | Action |
|---|---|
| 1 | CPU issues the request, then continues executing other instructions. |
| 2 | When ready, the device raises an interrupt on the control bus. |
| 3 | At the end of the current FDE cycle the CPU detects it, saves its state (pushes registers/PC onto the stack). |
| 4 | The CPU runs the Interrupt Service Routine (ISR) to perform the transfer. |
| 5 | The CPU restores its state from the stack and resumes the original task exactly where it paused. |
Advantages: the CPU is not wasted — it works while waiting; ideal for slow, sporadic devices (keyboard, mouse, printer). Disadvantages: more complex hardware (interrupt controller) and software (ISR); per-interrupt overhead of saving/restoring state; and the need to arbitrate interrupt priorities when several devices interrupt at once.
The interrupt-handling sequence is worth tracing precisely, because it is the same mechanism described in the processor lesson. Suppose the CPU is part-way through a calculation when a keyboard key is pressed:
# CPU is running the main program (PC points somewhere in it)
...
# --- a key is pressed; the keyboard controller raises an interrupt ---
AT END OF CURRENT FDE CYCLE:
IF interrupt pending AND interrupts enabled THEN
push PC onto stack # remember where to return
push working registers # save the interrupted context
disable lower-priority interrupts
PC ← address of keyboard ISR # jump to the handler
# --- ISR runs ---
read DATA_REGISTER of keyboard controller # collect the key code
store key code in the input buffer
acknowledge / clear the interrupt
# --- ISR ends ---
re-enable interrupts
pop working registers # restore the saved context
pop PC # resume exactly where we left off
ENDIF
# main program continues as if nothing happened
Two details earn marks. First, the interrupt is checked at the end of the current FDE cycle, never mid-instruction, so an instruction always completes atomically. Second, saving and restoring state via the stack is what makes the interruption invisible to the main program — it resumes with identical register contents, oblivious to the diversion. The cost is the time spent pushing and popping that state, which is the per-interrupt overhead noted above.
Imagine a keyboard that produces a character only a few times per second, while the CPU executes billions of instructions per second. Under polling, the CPU might read the status register millions of times between keystrokes, finding "not ready" every time — millions of wasted instructions per character. Under interrupt-driven I/O, the CPU does useful work the entire time and is disturbed only when a key actually arrives, paying just the one-off cost of entering and leaving the ISR. For such a slow, sporadic device the interrupt approach is overwhelmingly more efficient. The one situation where polling can win is a device that is almost always ready and very fast, where the interrupt overhead would exceed the cost of a quick status check — but that is rare in practice.
Subscribe to continue reading
Get full access to this lesson and all 10 lessons in this course.