Operating Systems, Inputs and Interrupts
Hello everyone, In today’s article I’m going to talk about how to program our OS to get input from the keyboard. In order to get input from the keyboard first, we have to configure the CPU to recognize interrupts.
An interrupt is a response by the processor to an event that needs attention from the software. An interrupt condition alerts the processor and serves as a request for the processor to interrupt the currently executing code when permitted so that the event can be processed on time. If the request is accepted, the processor responds by suspending its current activities, saving its state, and executing a function called an interrupt handler (or an interrupt service routine, ISR) to deal with the event. This interruption is temporary, and, unless the interrupt indicates a fatal error, the processor resumes normal activities after the interrupt handler finishes. Interrupts are commonly used by hardware devices to indicate electronic or physical state changes that require attention. Interrupts are also commonly used to implement computer multitasking, especially in real-time computing. Systems that use interrupts in these ways are said to be interrupt-driven.
The Interrupt Descriptor Table (IDT) is a data structure used by the x86 architecture to implement an interrupt vector table. The IDT is used by the processor to determine the correct response to interrupts and exceptions. The use of the IDT is triggered by three types of events: hardware interrupts, software interrupts, and processor exceptions, which together are referred to as interrupts.
The IDT consists of 256 interrupt vectors–the first 32 (0–31 or 0x00–0x1F) of which are used for processor exceptions.
An interrupt handler, also known as an interrupt service routine or ISR, is a special block of code associated with a specific interrupt condition. Interrupt handlers are initiated by hardware interrupts, software interrupt instructions, or software exceptions, and are used for implementing device drivers or transitions between protected modes of operation, such as system calls. The traditional form of the interrupt handler is the hardware interrupt handler. Hardware interrupts arise from electrical conditions or low-level protocols implemented in digital logic, are usually dispatched via a hard-coded table of interrupt vectors, asynchronously to the normal execution stream (as interrupt masking levels permit), often using a separate stack, and automatically entering into a different execution context (privilege level) for the duration of the interrupt handler’s execution. In general, hardware interrupts and their handlers are used to handle high-priority conditions that require the interruption of the current code the processor is executing. Interrupt handlers have a multitude of functions, which vary based on what triggered the interrupt and the speed at which the interrupt handler completes its task.
For example, pressing a key on a computer keyboard, or moving the mouse, triggers interrupts that call interrupt handlers which read the key, or the mouse’s position, and copy the associated information into the computer’s memory.
In computing, a programmable interrupt controller (PIC) is an integrated circuit that helps a microprocessor (or CPU) handle interrupt requests (IRQ) coming from multiple different sources (like external I/O devices) which may occur simultaneously. It helps prioritize IRQs so that the CPU switches execution to the most appropriate interrupt handler (ISR) after the PIC assesses the IRQ’s relative priorities. Common modes of interrupt priority include hard priorities, rotating priorities, and cascading priorities. PICs often allow mapping input to outputs in a configurable way.
On the PC architecture PIC are typically embedded into a southbridge chips whose internal architecture is defined by the chipset vendor’s standards.
So in order to get the OS to recognize keystrokes first, we have to configure it to recognize interrupts. This can be done by using interrupt handlers and interrupt Descriptor Tables. In the IDT interrupts are numbered and they and the handler for interrupt i is defined at the ith position in the table. When an interrupt occurs the CPU will enter some information about the interrupt into the stack and then look up the appropriate interrupt handler in IDT and execute it. Once the interrupt handler is done, it uses the iret instruction to return. The instruction iret expects the stack to be the same as at the time of the interrupt. Therefore, any values pushed onto the stack by the interrupt handler must be popped.
The interrupt handler has to be written in assembly code since all registers that the interrupt handlers used 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. But writing all code related to interrupt handler in assembly is hard so we’ll create the handler in assembly, calls it using C then restore the registers.
Since the CPU does not push the interrupt number on the stack it is a little hard to write a generic interrupt handler. So we will use macros to do it. Since not all interrupts produce an error code the value 0 will be added as the “error code” for interrupts without an error code.
Note that the specific CPU interrupts that put an error code on the stack are 8, 10, 11, 12, 13, 14 and 17. This error code can be used by the interrupt handler to get more information on what has happened. This generic interrupt handler will push the registers on the stack, Call the C function, Pop the registers and execute
iret to return to the interrupted code.
The IDT is loaded with the
lidt assembly code instruction which takes the address of the first element in the table. It is easiest to wrap this in an assembly function and use it in C.
To start using hardware interrupts we have to configure the programmable interrupt controller. The PIC maps the signals from hardware to interrupts.
The keyboard does not directly send ASCII characters, but it sends scan codes. The scan code sent by the keyboard represents the data relevant to both the press and release of the keyboard button to the I/O port
So we have to grab that data and convert it into an ASCII character. Also, note that the keyboard will not send you any more interrupts until you read the scan code from the keyboard.
After all of this is done we will add the new
.c files to the object line of the
makefile and run it. If everything has gone well the key you press will be shown on the screen and they will also be recorded in the
Helin E, Renberg A. (2015). The little book about OS development: https://littleosbook.github.io/
Os Dev Wiki https://wiki.osdev.org/Main_Page
Thank you for reading and I will be back next week with the next article.