Building your own Operating System (Week 05)
This is the fifth article in this series. In this article, I’m going to talk about interrupts and inputs. In the previous article, I explained how to integrate segmentation and you can check it out by clicking here. For those coding and implementation staff, I’m following the guide “The little book about OS development” by Erik Helin and Adam Renberg.
Now we are going to get some input from users and produce output. An interrupt occurs when a hardware device, such as the keyboard, the serial port or the timer, signals the CPU that the state of the device has changed. The CPU itself can also send interrupts due to program errors. Finally, there is also software interrupts, which are interrupts that are caused by the “int” assembly code instruction, and they are often used for system calls.
Interrupts are handled via the Interrupt Descriptor Table (IDT). The IDT describes a handler for each interrupt. 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. 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.
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
The interrupt handler has to be written in assembly code since all registers that the interrupt handlers use must be preserved by pushing them onto the stack. This is because the code that was interrupted doesn’t know about the interrupt and will therefore expect that its registers stay the same.
So, interrupts handler assembly code needs to be like this.
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 does not generate ASCII characters, it generates scan codes. A scan code represents a button both presses and releases. The scan code representing the just pressed button can be read from the keyboard’s data I/O port which has the address “0x60”. To do that you can declare necessary functions and variables like this.
Here’s the “keyboard.c” file with the definitions of previously declared functions.
Now you need to configure the “Makefile” properly like this to run your OS.
Also, you need to call the “interrupts_install_idt” function in the “kmain.c” file. Now you can run your OS and enter some keyboard inputs into it. Then you can see that keyboard input written in the “com1.out” file like this as I entered “krishan” to the input.
Note: If you received an error like this when you running your OS, you need to change “section .text:” to “section .text” in the “loader.s” file.
I think you all get a good idea about interrupts and inputs. You can check my Github Repo using this link.
Thank you for reading and I will be back next week with part 06 of this article series.