Introduction to Interrupts How we can intervene in the CPU’s interrupt-handling

advertisement
Introduction to Interrupts
How we can intervene in the
CPU’s interrupt-handling
mechanism (in real-mode)
Recall ‘fetch-execute’ cycle
• The processor repeatedly performs the
following sequence of actions:
– Fetch the next instruction (at address CS:IP)
– Advance the instruction-pointer register (IP)
– Decode the just-fetched instruction
– Execute the just-decoded operation
• But devices may interrupt this cycle (by
raising a voltage on the INTR input-pin)
External Interrupt Requests
SMI#
NMI
CPU
INTR
EFLAGS
IF-bit
As soon as the CPU has completed execution one instruction
(and before it starts to execute the next one), it will sample the
voltages on these input-pins. If it detects a signal on the INTR
line, and if the IF-bit is set to 1 in the FLAGS register, then the
CPU will defer executing the next instruction, and instead will
transfer control to an Interrupt Service Routine (ISR).
Response to INTR
• These are the actions taken by the CPU if
it detects an INTR signal (while IF=1):
– Push the FLAGS register onto the stack
– Clear IF and TF bits in the FLAGS register
– Push the CS and IP registers onto the stack
– Query the PIC to obtain the IRQ ID-number
– Lookup the corresponding Interrupt-Vector
– Load CS and IP with the vector’s values
Interrupt Vector Table (IVT)
0x00
0x08
0x10
0x18
0x20
0x28
0x30
0x38
0x40
0x48
0x50
0x58
0x60
0x68
0x70
0x78
0x01
0x09
0x11
0x19
0x21
0x29
0x31
0x39
0x41
0x49
0x02
0x0A
0x12
0x1A
0x22
0x2A
0x32
0x3A
0x42
0x03
0x0B
0x13
0x1B
0x23
0x2B
0x33
0x3B
0x43
0x04
0x0C
0x14
0x1C
0x24
0x2C
0x34
0x3C
0x05
0x0D
0x15
0x1D
0x25
0x2D
0x35
0x3D
0x06
0x0E
0x16
0x1E
0x26
0x2E
0x36
0x3E
Here is the User Timer-Tick Interrupt-Vector
(it’s the one we will temporarily modify to point
at our own “custom” Interrupt Service Routine
0x07
0x0F
0x17
0x1F
0x27
0x2F
0x37
0x3F
An Interrupt Vector’s layout
Each vector is a doubleword (4 bytes)
Byte 0
Byte 1
Byte 2
Byte 3
OFFSET
SEGMENT
Least significant word
goes into register IP
Most significant word
goes into register CS
Conditions on entering an ISR
•
•
•
•
Further device-interrupts are disabled
On the stack are saved IP, CS and FLAGS
All the other CPU registers are unmodified
The suspended program can be resumed,
at the point where it was interrupted, using
the CPU’s special ‘iret’ instruction
• ‘iret’ pops the three topmost words off the
stack and back into IP, CS, and FLAGS
Preserving interrupted context
• It is vital for an interrupted program to be
able to resume with exactly the same CPU
state it had before being suspended
• The CPU saves only a ‘minimal’ amount of
the program-context (FLAGS, CS, and IP)
• It’s a responsibility of the ISR programmer
to save and restore any other registers it
may be necessary to modify within the ISR
The IBM-PC ROM-BIOS
• When the IBM-PC was introduced (1981),
the full source-code for its ROM-BIOS was
published (as a Technical Reference)
• That code was written entirely in assembly
language (for the 8086 processor)
• It was approximately 5,400 lines in length
• It included Interrupt Service Routines for
each of the standard peripheral devices
An ISR example
• One of the standard peripheral devices is
the Programmable Interval-Timer (PIT)
• During the Power-On Self-Test, the timer
is programmed to generate an interrupt at
regular intervals (approximately 18.2 times
per second)
• These ‘timer-tick’ interrupts are used for
timekeeping purposes by the ROM-BIOS
Four timer-tick actions
• By studying the IBM source-code, we see
that four actions are performed each time
the PIT generates a ‘timer-tick’ interrupt:
– The double-word counter (located at 40:6C)
gets incremented; if it reaches its maximum,
it’s reset to zero and a ‘rollover’ flag is set.
– The motor_wait counter (byte at 40:40) gets
decremented; when it reaches 0, the floppy
disk-drive motors, if on, are turned off
Timer ISR (continued)
• A software interrupt (INT-0x1C) executes,
to allow applications an easy method for
implementing customized timing activities
• A EOI-command (End-Of-Interrupt) is sent
to the system’s Programmable Interrupt
Controller (PIC) informing it that it’s now
ok to activate requests for interrupts from
the peripheral devices
An In-Class Exercise
• We want to experiment with ‘hooking’ the
user timer-tick interrupt (INT-0x1C)
• By default the IVT contains an interrupt
vector for INT-0x1C pointing to a ‘dummy’
interrupt service routine (just an ‘iret’)
• We can overwrite this IVT entry with our
own entry, a vector that points to a custom
service-routine that performs some action
we have designed (e.g., show time-of-day)
The bootsector ‘squeeze’
• As we know, there isn’t much room for us
to include much custom code in a diskette
boot-sector (only 512 bytes total)
• But we CAN put code into a bootsector
that will load lots more code, from other
diskette-sectors, into the physical ram
• This is how an operating system boots up
• We can call the BIOS to read disk-sectors
INT-0x13 services
• Six service-routines for floppy diskettes:
– #0: reset the floppy diskette controller
– #1: return status of diskette controller
– #2: read sector(s) from floppy diskette
– #3: write sector(s) to floppy diskette
– #4: verify sector(s) on floppy diskette
– #5: format a track on a floppy diskette
• More details on Ralf Brown’s Interrupt List
1440KB Diskette organization
Heads (2 per disk)
Sector-size
(512 bytes)
Tracks (80 per head)
Sectors (18 per track)
Terminology and Numbering
• The Disk-Controller locates its data-blocks
using a so-called CHR coordinate-system
– Cylinders: 0..79
– Heads: 0..1
– Records: 1..18
• The floppy drives are numbered 0, 1, 2, …
How to read from diskette
•
•
•
•
•
•
•
•
INT-0x13, function 2 (Read sectors):
ES:BX = destination of the data-transfer
DL = disk-drive number (0, 1, … )
CH = cylinder-number: 0, 1, … , 79
DH = head-number: 0, 1
CL = record-number: 1, 2, … , 18
AL = record-count: 1, 2, … , 18
AH = function-number: 0x02
Demo ‘trackldr.s’
• This is a bootsector program that loads the
remaining seventeen diskette sectors from
track that contains the boot-sector
• These sectors are placed consecutively in
memory, starting from address 0x10000
• This loader checks for a special signature
(0xABCD) before making a far-call to the
designated entry-point (at 1000:0002):
callf #0x0002, #0x1000
The ‘usertick.s’ demo
• The ‘trackldr.s’ loader is assembled and
installed as the diskette’s ‘boot’ sector:
– $ as86 trackldr.s –b trackldr.b
– $ dd if=trackldr.b of=/dev/fd0
• This program is assembled and installed
as the second sector on the floppy disk
– $ as86 usertick.s –b usertick.b
– $ dd if=usertick.b of=/dev/fd0 seek=1
What ‘usertick.s’ does
•
•
•
•
Copies interrupt-vector 0x1C to save-area
Installs new entry as interrupt-vector 0x1C
Waits for the user to press any key
While waiting, the current time-of-day gets
displayed on the screen’s top-right corner
• When any key is hit, the original interruptvector 0x1C is restored to the IVT, and
control returns to the ‘trackldr.s’ program
How time-of-day is found
• The ROM-BIOS DATA-AREA maintains a
count of the number of timer-ticks since
the day began (at midnight)
• Divide ticks by 18.2 (ticks-per-second) to
get the number of seconds since midnight
• Divide seconds by 60 to get minutes today
• Divide minutes by 60 to get hours today
• Divide hours by 12 to get halfdays today
• Remainder (0 or 1) will tell us AM or PM
Drawing the string to VRAM
•
•
•
•
string-format: “ hh:mm:ss xm “ (13 chars)
Screen’s memory-segment: 0xB800
Cell’s offset (after top-right corner) = 80 * 2
String’s starting offset: (80-13) * 2 = 134
25
rows
Video
screen
80 columns
Modifying our ISR
• We can modify the actions in our interrupt
service routine so as to display a different
item of timing information
• Instead of displaying the time-of-day, we
could show how much time has elapsed
since our computer was last restarted
• To do this, we would need to access the
Pentium’s TimeStamp Counter register
Time Stamp Counter
• The Time Stamp Counter (TSC) is a 64-bit
register located, inside the CPU, and reset
to zero when the power is first turned on
• The TSC is automatically incremented with
each CPU clock-cycle
• Our CPUs operate at 2.4 GHz
• We can compute the time since Power-On
if we divide the TSC value by 2.4 billion
The RDTSC instruction
• A special CPU instruction is used to read
the Time Stamp Counter’s current value
• The name of that instruction is ‘rdtsc’
• Our ‘as86’ assembler doesn’t recognize
this mnemonic opcode (it’s too new)
• But we can still use it, by including its
machine-code in our instruction-stream:
.BYTE
0x0F, 0x31
; RDTSC
How RDTSC works
• When the CPU executes ‘rdtsc’, the value
from register TSC (64-bits) is copied into
the EDX:EAX register-pair
• So if we want to divide by 2,400,000,000,
we can then use this code-fragment:
mov ecx, #2400000000
; setup divisor
div
ecx
; do division
; quotient is now in EAX, regainder in EDX
Exercise
• Revise the ‘usertick.s’ program so that it
will display how long it’s been since your
computer was started, like this:
hh:mm:ss since power-on
Download