Memory Bus
I/O Bus
I/O Controller
I/O Device
I/O-mapped memory
Programmed I/O (PIO)
Controller/device is much slower than cpu
I/O Controller cannot initiate communication
Sometimes CPU asks for data
Sometimes controller receives data for the CPU
CPU must request data, but data may not be available
Option: polling
xxxxxxxxxx
91int pollDeviceForValue() {
2 volatile int *ready = 0x80000100;
3 volatile int *value = 0x80000104;
4 int v;
5 while (!*ready) {}
6 v = *value;
7 *ready = 0;
8 return v;
9}
xxxxxxxxxx
71int readyValueForCPUPoll(int v) {
2 volatile int *ready = 0x80000100;
3 volatile int *value = 0x80000104;
4 while (*ready) {}
5 *value = v;
6 *ready = 1;
7}
Reading (or writing) I/O memory is slow
Polling is ok if:
CPU and controllers are independent processors
Direct Memory Access (DMA)
CPU Interrupts
Controller can signal the CPU when data is available
CPU checks for interrupts on every cycle
CPU jumps to controller's Interrupt Service Routine if its interrupting
New special-purpose CPU registers
Modified fetch-execute cycle
xxxxxxxxxx
81while(true) {
2 if(isDeviceInterrupting) {
3 r[5] <- r[5] - 4; m[r[5]] <- r[6]; r[6] <- PC;
4 PC <- interruptVectorBase[interruptControllerID];
5 }
6 fetch();
7 execute();
8}
xxxxxxxxxx
21t <- r[6]; r[6] <- m[r[5]]; r[5] <- r[5] + 4;
2isDeviceInterrupting <- 0; PC <- t;
Hard disk is one possible device
Disk stores blocks of data
PIO is used to request data
Consider a program that reads data from a disk
A read can be considered to have three parts:
These steps must happen in order (sequence)
xxxxxxxxxx
101char checksumDiskData(int blockNum, int numBytes) {
2 char buf[numBytes];
3 char xsum = 0;
4
5 diskRead(buf, blockNum, numBytes);
6 for (int i = 0; i < numBytes; i++)
7 xsum ^= buf[i];
8
9 return xsum
10}
The function will be idle while waiting for data
We can separate the code into two parts
Request something
xxxxxxxxxx
21char buf[numBytes];
2diskRead(buf, blockNum, numBytes);
Handle data once request finished
xxxxxxxxxx
31char xsum = 0;
2for (int i = 0; i < numBytes; i++)
3 xsum ^= buf[i];
Events are "things" that cause asynchronous code to run
Event Handlers are the code that runs when event occurs
Handlers are registered to a specific event
Events are fired to trigger the execution of the handler
In code:
xxxxxxxxxx
121void computeCheckSum(char *buf, int blockNum, int numBytes) {
2 char xsum = 0;
3 for (int i = 0; i < numBytes; i++)
4 xsum ^= buf[i];
5 // save checksum somewhere
6}
7
8void checksumDiskData(int blockNum, int numBytes) {
9 char buf[numBytes];
10
11 diskRead(buf, blockNum, numBytes, computeChecksum);
12}
xxxxxxxxxx
391
2void (*interruptVector [MAX_DEVICES])();
3
4int diskID = 4;
5interruptVector [diskID] = diskISR;
6
7struct handler_dsc {
8 void (*handler) (char*, int, int);
9 char* buf;
10 int blockNum;
11 int numBytes;
12}
13
14int* diskAddress = (int*) 0x80001000;
15
16
17
18
19struct disk_ctl {
20 int op;
21 char* buf;
22 int blockNum;
23 int numBytes;
24}
25
26void diskISR() {
27 struct handler_dsc hd;
28 dequeue_handler (&hd);
29 hd.handler (hd.buf, bd.blockNum, hd.numBytes);
30}
31
32void diskRead(char* buf, int blockNum, int numBytes, void (*whenComplete) (char*, int, int)) {
33 struct disk_ctl* dc = (struct disk_ctl*) diskAddress;
34 enqueue_handler (whenComplete, buf, blockNum, numBytes);
35 dc->op = DISK_OP_READ;
36 dc->buf = buf;
37 dc->blockNum = blockNum;
38 dc->numBytes = numBytes;
39}
The original version of checksumDiskData returned a char
So we are forced to rethink our processs
Whatever was done with return value will now need to be done after handler runs
xxxxxxxxxx
191void printByte(char c) {
2 printf("0x%1x\n", c);
3}
4
5void computeCheckSum(char* buf, int blockNum, int numBytes,
6 void (*whenComplete) (char)) {
7 int xsum = 0;
8 for (int i = 0; i < numBytes; i++)
9 xsum ^= buf[i];
10 whenComplete(xsum);
11 free(buf);
12}
13
14void checksum(int blockNum, int numBytes, void (*whenComplete) (char)) {
15 char* buf = malloc (numBytes);
16 diskRead(buf, blockNum, numBytes, whenComplete, computeChecksum)
17}
18
19checksum(1234, 4096, printByte);
Humans like synchrony
Computer systems are asynchronous
The disk controller takes 10-20 milliseconds (10-3s) to read a block
CPU can execute millions of instructions while waiting for the disk to complete one read
we must allow the CPU to do other work while waiting for I/O completion
many devices send unsolicited data at unpredictable times
Asynchrony makes programs more difficult to write
Some languages simplify event-driven programming
Some abstractions can provide illusion of synchrony
Best option is still "research in progress"