Create New Operating System-Week 5

A.M.V.N.Attanayaka
6 min readAug 20, 2021

hello everyone, I am back this is our firth week to creating own OS. lets start.

In a previous weeks i explain how OS can produce output. it would be nice if it also could get some input using keyboard. so this week try to do that with our own os.

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. 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

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 hander in the IDT and jump to it. The stack at the time of the interrupt will look like the following:

[esp + 12] eflags
[esp + 8] cs
[esp + 4] eip
[esp] error code?

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 (see the figure above). Therefore, any values pushed onto the stack by the interrupt handler must be popped. Before returning, iret restores eflags by popping the value from the stack and then finally jumps to cs:eip as specified by the values on the stack.

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

Creating a handler in assembly code that saves the registers, calls a C function, restores the registers and finally executes iret is a good idea!

The C handler should get the state of the registers, the state of the stack and the number of the interrupt as arguments. The following definitions can for example be used:

struct cpu_state {
unsigned int eax;
unsigned int ebx;
unsigned int ecx;
.
.
.
unsigned int esp;
} __attribute__((packed));
struct stack_state {
unsigned int error_code;
unsigned int eip;
unsigned int cs;
unsigned int eflags;
} __attribute__((packed));
void interrupt_handler(struct cpu_state cpu, struct stack_state stack, unsigned int interrupt);

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. This section will use macros to show how it can be done.

%macro no_error_code_interrupt_handler %1
global interrupt_handler_%1
interrupt_handler_%1:
push dword 0 ; push 0 as error code
push dword %1 ; push the interrupt number
jmp common_interrupt_handler ; jump to the common handler
%endmacro

%macro error_code_interrupt_handler %1
global interrupt_handler_%1
interrupt_handler_%1:
push dword %1 ; push the interrupt number
jmp common_interrupt_handler ; jump to the common handler
%endmacro

common_interrupt_handler: ; the common parts of the generic interrupt handler
; save the registers
push eax
push ebx
.
.
.
push ebp

; call the C function
call interrupt_handler

; restore the registers
pop ebp
.
.
.
pop ebx
pop eax

; restore the esp
add esp, 8

; return to the code that got interrupted
iret

no_error_code_interrupt_handler 0 ; create handler for interrupt 0
no_error_code_interrupt_handler 1 ; create handler for interrupt 1
.
.
.
error_code_handler 7 ; create handler for interrupt 7
.
.
.

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. It is easiest to wrap this instruction and use it from C:

global  load_idt    ; load_idt - Loads the interrupt descriptor table (IDT).
; stack: [esp + 4] the address of the first entry in the IDT
; [esp ] the return address
load_idt:
mov eax, [esp+4] ; load the address of the IDT into register eax
lidt eax ; load the IDT
ret ; return to the calling function

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.

The hardware interrupts are shown in the table below:

Acknowledging a PIC interrupt is done by sending the byte 0x20 to the PIC that raised the interrupt. Implementing a pic_acknowledge function can thus be done as follows:

#include "io.h"    #define PIC1_PORT_A 0x20
#define PIC2_PORT_A 0xA0
/* The PIC interrupts have been remapped */
#define PIC1_START_INTERRUPT 0x20
#define PIC2_START_INTERRUPT 0x28
#define PIC2_END_INTERRUPT PIC2_START_INTERRUPT + 7
#define PIC_ACK 0x20 /** pic_acknowledge:
* Acknowledges an interrupt from either PIC 1 or PIC 2.
*
* @param num The number of the interrupt
*/
void pic_acknowledge(unsigned integer interrupt)
{
if (interrupt < PIC1_START_INTERRUPT || interrupt > PIC2_END_INTERRUPT) {
return;
}
if (interrupt < PIC2_START_INTERRUPT) {
outb(PIC1_PORT_A, PIC_ACK);
} else {
outb(PIC2_PORT_A, PIC_ACK);
}
}

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 address 0x60. How this can be done is shown in the following example:

#include "io.h"    #define KBD_DATA_PORT   0x60    /** read_scan_code:
* Reads a scan code from the keyboard
*
* @return The scan code (NOT an ASCII character!)
*/
unsigned char read_scan_code(void)
{
return inb(KBD_DATA_PORT);
}

now you can run your os using “make run” command.you can give keyboard input and see it using “cat com1.out” command.

but if you find this type of errer you need to remove this “:” mark.

input keyboard inputs here.

you can see it like this way.

This is end of 5th week making ow os. hope to see you again very soon.

hope you now you can give keyboard input and also you can see my git hub folder using This link.

Thankyou!

--

--