Let’s make your own operating system (#week05_Interrupt and inputs)
This is the fifth article in this series. In this week I’m going to develop our operating system in to next stage which can handle the interrupts. In the last article I discuss about how to integrate segmentation.
Interrupts Handlers
Interrupts are handled via the Interrupt Descriptor Table (IDT). The IDT describes a handler for each interrupt. The interrupts are numbered (0–255) and the handler for interrupt i is defined at the ith position in the table. There are three different kinds of handlers for interrupts:
- Task handler
- Interrupt handler
- Trap handler
The task handlers use functionality specific to the Intel version of x86, so they won’t be covered here (see the Intel manual [33], chapter 6, for more info). The only difference between an interrupt handler and a trap handler is that the interrupt handler disables interrupts, which means you cannot get an interrupt while at the same time handling an interrupt. In this book, we will use trap handlers and disable interrupts manually when we need to
Creating an Entry in the IDT
An entry in the IDT for an interrupt handler consists of 64 bits. The highest 32 bits are shown in the figure below:
Bit: | 31 16 | 15 | 14 13 | 12 | 11 | 10 9 8 | 7 6 5 | 4 3 2 1 0 |
Content: | offset high | P | DPL | 0 | D | 1 1 0 | 0 0 0 | reserved |
The lowest 32 bits are presented in the following figure:
Bit: | 31 16 | 15 0 |
Content: | segment selector | offset low |
A description for each name can be found in the table below:
The offset is a pointer to code (preferably an assembly code label). For example, to create an entry for a handler whose code starts at 0xDEADBEEF
and that runs in privilege level 0 (therefore using the same code segment selector as the kernel) the following two bytes would be used:
0xDEAD8E00
0x0008BEEF
If the IDT is represented as an unsigned integer idt[512]
then to register the above example as a handler for interrupt 0 (divide-by-zero), the following code would be used:
idt[0] = 0xDEAD8E00
idt[1] = 0x0008BEEF
Handling an Interrupt
When an interrupt occurs the CPU will push some information about the interrupt onto the stack, then look up the appropriate interrupt handler in the IDT and jump to it. To do that we can use a C handler. The C handler should get the state of the registers, the state of the stack and the number of the interrupt as arguments.
So, we use the “interrupts.h” file to declare necessary functions and those structures.
Here’s the “interrupts.c” file with the definitions of previously declared functions.
Creating a Generic Interrupt Handler
Since the CPU does not push the interrupt number on the stack it is a little tricky to write a generic interrupt handler. The following code shows an example of how this can be done. Store it inside interrupt_handler.s file.
The common_interrupt_handler
does the following:
- Push the registers on the stack.
- Call the C function
interrupt_handler
. - Pop the registers from the stack.
- Add 8 to
esp
(because of the error code and the interrupt number pushed earlier). - Execute
iret
to return to the interrupted code.
Loading the IDT
The IDT is loaded with the “lidt” assembly code instruction which takes the address of the first element in the table. To do that you can use this assembly code.
Programmable Interrupt Controller (PIC)
To start using hardware interrupts you must first configure the Programmable Interrupt Controller (PIC). The PIC makes it possible to map signals from the hardware to interrupts. The reasons for configuring the PIC are:
- Remap the interrupts. The PIC uses interrupts 0–15 for hardware interrupts by default, which conflicts with the CPU interrupts. Therefore the PIC interrupts must be remapped to another interval.
- Select which interrupts to receive. You probably don’t want to receive interrupts from all devices since you don’t have code that handles these interrupts anyway.
- Set up the correct mode for the PIC.
Every interrupts from the PIC has to be acknowledged that is, sending a message to the PIC confirming that the interrupt has been handled. If this isn’t done the PIC won’t generate any more interrupts.
You can declare necessary functions and variables like this.
Here’s the “pic.c” file with the definitions of previously declared functions.
Reading Input from the Keyboard
The keyboard creates scan codes rather than ASCII characters. A scan code describes a button, both when it is pressed and when it is released. The scan code for the recently pushed button may be retrieved from the data I/O port on the keyboard, which contains the address 0x60. The following example demonstrates how this may be done:
All the things implement by following C code Keyboard. c file:
The next step is to create a function that converts a scan code to an ASCII character. Andries Brouwer provides a nice guide if you wish to map the scan codes to ASCII characters like on an American keyboard. You must call pic to acknowledge the conclusion of the keyboard interrupt handler since the keyboard interrupt is raised by the PIC. Also, until you read the scan code from the keyboard, the keyboard will not send you any more interruptions.
Thank you for reading….