38
ROLL YOUR OWN TOY UNIX-CLONE OS By Ahmed Essam www.eramax.org

Roll your own toy unix clone os

  • Upload
    eramax

  • View
    2.615

  • Download
    2

Embed Size (px)

DESCRIPTION

Roll your own toy unix clone osby Ahmed EssamFaculty of Computer and Information Assiut University

Citation preview

Page 1: Roll your own toy unix clone os

ROLL YOUR OWN

TOY UNIX-CLONE

OS

By Ahmed Essamwww.eramax.org

Page 2: Roll your own toy unix clone os

Table of contents

1. Environment setup2. Genesis3. The Screen4. The GDT and IDT

Page 3: Roll your own toy unix clone os

Setting Environment

To compile and run the sample code we need to have GCC Ld NASM GNU Make

Page 4: Roll your own toy unix clone os

Setting Environment (2)

To Run the OS we need a Virtual Machine Emulator such as VirtualBox Qemu Bochs

Page 5: Roll your own toy unix clone os

(boot loader )1. Environment setup This tutorial is not a bootloader tutorial. We

will be using GRUB to load our kernel. To do this, we need a floppy disk image with GRUB preloaded onto it.

What is GRUBGNU GRUB is a Multiboot boot loader.boot loader is the first software program that runs when a computer starts. It is responsible for loading and transferring control to the operating system kernel software .

Page 6: Roll your own toy unix clone os

(VM setup )1. Environment setup

We will use Bochs (an open-source x86-64 emulator).

We need a bochs configuration file (bochsrc.txt).

megs: 32floppya: 1_44="myOs2.bin", status=insertedboot: floppylog: bochsout.txtclock: sync=realtimecpu: ips=500000

keyboard_paste_delay: 100000

Page 7: Roll your own toy unix clone os

Useful scripts

Makefilemaking (compiling) our project.

Link.ldlink files together into one ELF binary (Kernel).

update_image.shpoke your new kernel binary into the floppy image file.

run_bochs.shmounts the correct loopback device, runs bochs, then unmounts.

Page 8: Roll your own toy unix clone os

2 .Genesis2.1 - The boot code

MBOOT_PAGE_ALIGN    equ 1<<0    ; Load kernel and modules on a page boundaryMBOOT_MEM_INFO      equ 1<<1    ; Provide your kernel with memory infoMBOOT_HEADER_MAGIC  equ 0x1BADB002 ; Multiboot Magic value; NOTE: We do not use MBOOT_AOUT_KLUDGE. It means that GRUB does not; pass us a symbol table.MBOOT_HEADER_FLAGS  equ MBOOT_PAGE_ALIGN | MBOOT_MEM_INFOMBOOT_CHECKSUM      equ -(MBOOT_HEADER_MAGIC + MBOOT_HEADER_FLAGS)

[BITS 32]                       ; All instructions should be 32-bit.

[GLOBAL mboot]                  ; Make 'mboot' accessible from C.[EXTERN code]                   ; Start of the '.text' section.[EXTERN bss]                    ; Start of the .bss section.[EXTERN end]                    ; End of the last loadable section.

mboot:  dd  MBOOT_HEADER_MAGIC        ; GRUB will search for this value on each                                ; 4-byte boundary in your kernel file  dd  MBOOT_HEADER_FLAGS        ; How GRUB should load your file / settings  dd  MBOOT_CHECKSUM            ; To ensure that the above values are correct   

Page 9: Roll your own toy unix clone os

2.1 - The boot code (Cont)

  dd  mboot                     ; Location of this descriptor  dd  code                      ; Start of kernel '.text' (code) section.  dd  bss                       ; End of kernel '.data' section.  dd  end                       ; End of kernel.  dd  start                     ; Kernel entry point (initial EIP).

[GLOBAL start]                  ; Kernel entry point.[EXTERN main]                   ; This is the entry point of our C code

start:  push    ebx                   ; Load multiboot header location

  ; Execute the kernel:  cli                         ; Disable interrupts.  call main                   ; call our main() function.  jmp $                       ; Enter an infinite loop, to stop the processor                              ; executing whatever rubbish is in the memory                              ; after our kernel!

Page 10: Roll your own toy unix clone os

2.3 .Adding some C code

// main.c

int main(struct multiboot *mboot_ptr){  // Kernel Code.  return 0xDEADBABA;}

Page 11: Roll your own toy unix clone os

common.c functions for writing to and reading from the I/O bus, and some typedefs :

typedef unsigned int   u32int;typedef          int   s32int;typedef unsigned short u16int;typedef          short s16int;typedef unsigned char  u8int;typedef          char  s8int;

void outb(u16int port, u8int value){asm volatile ("outb %1, %0" : : "dN" (port), "a" (value)); }u8int inb(u16int port){   u8int ret;   asm volatile("inb %1, %0" : "=a" (ret) : "dN" (port));   return ret;}u16int inw(u16int port){   u16int ret;   asm volatile ("inw %1, %0" : "=a" (ret) : "dN" (port));   return ret;}

Page 12: Roll your own toy unix clone os

3 .The Screen

3.1. The theory Your kernel gets booted by GRUB in text

mode.That is, it has available to it a framebuffer

(area of memory) that controls a screen of characters (not pixels) 80 wide by 25 high, at address 0xB8000.

Framebuffer is not actually normal RAM. It is part of the VGA controller's dedicated video memory that has been memory-mapped via hardware into your linear address space.

Page 13: Roll your own toy unix clone os

3.1 .The theory (Cont)

The framebuffer is just an array of 16-bit words, each 16-bit value representing the display of one character. The offset from the start of the framebuffer of the word that specifies a character at position x, y is:

(y * 80 + x) * 2 8 bits are used to represent a character. foreground and background colours (4

bits each).

Page 14: Roll your own toy unix clone os

3.1 .The theory (Cont)

Page 15: Roll your own toy unix clone os

3.2.2 .The monitor code

Moving the cursorstatic void move_cursor()

Scrolling the screen static void scroll()

Writing a character to the screen void monitor_put(char c)

location = video_memory + (cursor_y*80 + cursor_x);*location = c | attribute;

Clearing the screenu16int blank = 0x20 /* space */ | (attributeByte << 8);

   for (i = 0; i < 80*25; i++)   {

         video_memory[i] = blank;   }

Page 16: Roll your own toy unix clone os

3.2.2 .The monitor code (Cont) Writing a string

void monitor_write(char *c){

   int i = 0;   while (c[i])   {

        monitor_put(c[i++]);    }}

//-------- Kernel Code :monitor_clear();monitor_write("Hello, world!");

Page 17: Roll your own toy unix clone os

The monitor

Page 18: Roll your own toy unix clone os

4 .The GDT and IDT

4.1. The Global Descriptor Table (theory)are arrays of flags and bit values describing the operation of the segmentation system.

Every memory access is evaluated with respect to a segment. That is, the memory address is added to the segment's base address, and checked against the segment's length.

Page 19: Roll your own toy unix clone os

GDT there is one thing that segmentation can do

that paging can't, and that's set the ring levels.

A ring is a privilege level - zero being the most privileged, and three being the least. Processes in ring zero are said to be running in kernel-mode, or supervisor-mode, because they can use instructions like sti and cli, something which most processes can't.

A segment descriptor carries inside it a number representing the ring level it applies to.

Page 20: Roll your own toy unix clone os

The Global Descriptor Table (practical)

A GDT entry looks likestruct gdt_entry_struct

{   u16int limit_low;           // The lower 16 bits of the limit.   u16int base_low;            // The lower 16 bits of the base.   u8int  base_middle;         // The next 8 bits of the base.   u8int  access;              // Access flags, determine what ring //this segment can be used in.   u8int  granularity;   u8int  base_high;           // The last 8 bits of the base.} __attribute__((packed));

Page 21: Roll your own toy unix clone os

GDT

u8int  access;     

P    Is segment present? (1 = Yes)DPL    Descriptor privilege level - Ring 0 - 3.DT    Descriptor typeType    Segment type : code segment / data segment.

Page 22: Roll your own toy unix clone os

GDT

To pass the GDT Table ,we pass the Pointer to this table to the CPU and pass its limit(length).

So we must use this struct : struct gdt_ptr_struct

{   u16int limit;               // The upper 16 bits of all selector limits.   u32int base;                // The address of the first gdt_entry_t }

Page 23: Roll your own toy unix clone os

GDT

gdt_entry_t gdt_entries[5];gdt_ptr_t   gdt_ptr;

static void init_gdt(){   gdt_ptr.limit = (sizeof(gdt_entry_t) * 5) - 1;   gdt_ptr.base  = (u32int)&gdt_entries;

   gdt_set_gate(0, 0, 0, 0, 0);                // Null segment   gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF); // Code segment   gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF); // Data segment   gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF); // User mode code segment   gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF); // User mode data segment

   gdt_flush((u32int)&gdt_ptr);}

Page 24: Roll your own toy unix clone os

GDT static void gdt_set_gate(s32int num, u32int base, u32int limit, u8int acc

ess, u8int gran){   gdt_entries[num].base_low    = (base & 0xFFFF);   gdt_entries[num].base_middle = (base >> 16) & 0xFF;   gdt_entries[num].base_high   = (base >> 24) & 0xFF;

   gdt_entries[num].limit_low   = (limit & 0xFFFF);   gdt_entries[num].granularity = (limit >> 16) & 0x0F;

   gdt_entries[num].granularity |= gran & 0xF0;   gdt_entries[num].access      = access;}

[GLOBAL gdt_flush]    ; Allows the C code to call gdt_flush().

gdt_flush:   mov eax, [esp+4]  ; Get the pointer to the GDT, passed as a parameter.   lgdt [eax]        ; Load the new GDT pointer

Page 25: Roll your own toy unix clone os

4.3 .The Interrupt Descriptor Table (theory)

There are times when you want to interrupt the processor. You want to stop it doing what it is doing, and force it to do something different. An example of this is when an timer or keyboard interrupt request (IRQ) fires.

The processor can register 'signal handlers' (interrupt handlers) that deal with the interrupt, then return to the code that was running before it fired. Interrupts can be fired externally, via IRQs, or internally, via the 'int n' instruction.

The Interrupt Descriptor Table tells the processor where to find handlers for each interrupt. It is very similar to the GDT. It is just an array of entries, each one corresponding to an interrupt number.

There are 256 possible interrupt numbers, so 256 must be defined. If an interrupt occurs and there is no entry for it (even a NULL entry is fine), the processor will panic and reset.

Page 26: Roll your own toy unix clone os

Faults, traps and exceptions The special, CPU-dedicated 32 interrupts :

0 - Division by zero exception1 - Debug exception2 - Non maskable interrupt3 - Breakpoint exception4 - 'Into detected overflow'5 - Out of bounds exception6 - Invalid opcode exception7 - No coprocessor exception8 - Double fault (pushes an error code)9 - Coprocessor segment overrun10 - Bad TSS (pushes an error code)11 - Segment not present (pushes an error code)12 - Stack fault (pushes an error code)13 - General protection fault (pushes an error code)14 - Page fault (pushes an error code)15 - Unknown interrupt exception16 - Coprocessor fault17 - Alignment check exception18 - Machine check exception19-31 - Reserved

Page 27: Roll your own toy unix clone os

4.4. The Interrupt Descriptor Table (practice)

// A struct describing an interrupt gate.struct idt_entry_struct{   u16int base_lo;             // The lower 16 bits of the address to //jump to when this interrupt fires.   u16int sel;                 // Kernel segment selector.   u8int  always0;             // This must always be zero.   u8int  flags;               // More flags. See documentation.   u16int base_hi;     // The upper 16 bits of the address to jump to.} __attribute__((packed));

struct idt_ptr_struct{   u16int limit;   u32int base; // The address of the first element in our idt_entry_t array.} __attribute__((packed));

Page 28: Roll your own toy unix clone os

IDT

The DPL describes the privilege level we expect to be called from.

The P bit signifies the entry is present.Any descriptor with this bit clear will cause a "Interrupt Not Handled" exception.

Page 29: Roll your own toy unix clone os

IDT extern void isr0 ();

...extern void isr31();

idt_entry_t idt_entries[256];idt_ptr_t   idt_ptr;

static void init_idt(){   idt_ptr.limit = sizeof(idt_entry_t) * 256 -1;   idt_ptr.base  = (u32int)&idt_entries;

   memset(&idt_entries, 0, sizeof(idt_entry_t)*256);//set all to null.

   idt_set_gate( 0, (u32int)isr0 , 0x08, 0x8E);   idt_set_gate( 1, (u32int)isr1 , 0x08, 0x8E);   ...   idt_set_gate(31, (u32int)isr32, 0x08, 0x8E);   idt_flush((u32int)&idt_ptr);

}

Page 30: Roll your own toy unix clone os

IDT

[GLOBAL idt_flush]    ; Allows the C code to call idt_flush().

idt_flush:   mov eax, [esp+4]  ; Get the pointer to the IDT, passed as a parameter.    lidt [eax]        ; Load the IDT pointer.   ret

Page 31: Roll your own toy unix clone os

IDT

Great! We've got code that will tell the CPU where to find our interrupt handlers - but we haven't written any yet!

When the processor receives an interrupt, it saves the contents of the essential registers (instruction pointer, stack pointer, code and data segments, flags register) to the stack. It then finds the interrupt handler location from our IDT and jumps to it.

some interrupts also push an error code onto the stack. We can't call a common function without a common stack frame, so for those that don't push an error code, we push a dummy one, so the stack is the same.

Page 32: Roll your own toy unix clone os

interrupt.s

ISR_NOERRCODE 0ISR_NOERRCODE 1...

Page 33: Roll your own toy unix clone os

interrupt.s ASM common handler function to handle interrupts : ; In isr.c

[EXTERN isr_handler]

; This is our common ISR stub. It saves the processor state, sets; up for kernel mode segments, calls the C-level fault handler,; and finally restores the stack frame.isr_common_stub:   pusha                    ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax

   mov ax, ds               ; Lower 16-bits of eax = ds.   push eax                 ; save the data segment descriptor

   mov ax, 0x10  ; load the kernel data segment descriptor   mov ds, ax   mov es, ax   mov fs, ax   mov gs, ax

   call isr_handler

   pop eax        ; reload the original data segment descriptor   mov ds, ax   mov es, ax   mov fs, ax   mov gs, ax

   popa                     ; Pops edi,esi,ebp...   add esp, 8     ; Cleans up the pushed error code and pushed ISR number   sti   iret           ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP

Page 34: Roll your own toy unix clone os

isr.c typedef struct registers

{   u32int ds;                  // Data segment selector   u32int edi, esi, ebp, esp, ebx, edx, ecx, eax; // Pushed by pusha.   u32int int_no, err_code;    // Interrupt number and error code (if applicable)   u32int eip, cs, eflags, useresp, ss; // Pushed by the processor automatically.} registers_t;

// This gets called from our ASM interrupt handler stub.void isr_handler(registers_t regs){   monitor_write("recieved interrupt: ");   monitor_write_dec(regs.int_no);   monitor_put('\n');}

Page 35: Roll your own toy unix clone os

Testing

// Kernel Code: asm volatile ("int $0x3");

asm volatile ("int $0x4"); Output:

Page 36: Roll your own toy unix clone os

References

http://www.jamesmolloy.co.uk http://wiki.osdev.org/Main_Page http://forum.osdev.org/ Intel® 64 and IA-32 Architectures

Software Developer's Manuals http://www.intel.com/products/processor/manuals/

Memory Modes : Real Mode and Protected Modehttp://3asfh.net/vb/showthread.php?t=37922

Page 37: Roll your own toy unix clone os

References(2)

Making a Simple C kernel with Basic printf and clearscreen Functionshttp://www.osdever.net/tutorials/basickernel.php

Page 38: Roll your own toy unix clone os

Thank You

Thank You .