Roll your own toy UNIX-clone OS<br />By Ahmed Essam<br />www.eramax.org<br />
Table of contents<br />	1. Environment setup2. Genesis3. The Screen4. The GDT and IDT<br />
Setting Environment<br />To compile and run the sample code we need to have <br /><ul><li>GCC
Ld
NASM
GNU Make</li></li></ul><li>Setting Environment (2)<br />To Run the OS we need a Virtual Machine Emulator such as <br /><ul...
Qemu
Bochs </li></li></ul><li>(boot loader)1. Environment setup<br />This tutorial is not a bootloader tutorial. We will be usi...
(VM setup)1. Environment setup<br />We will use Bochs (an open-source x86-64 emulator).<br />We need a bochs configuration...
Useful scripts<br />Makefile<br />	making (compiling) our project.<br />Link.ld<br />	link files together into one ELF bin...
2. Genesis2.1 - The boot code<br />MBOOT_PAGE_ALIGN    equ 1<<0    ; Load kernel and modules on a page boundaryMBOOT_MEM_I...
2.1 - The boot code (Cont)<br />  dd  mboot                     ; Location of this descriptor  dd  code                   ...
2.3. Adding some C code<br />// main.c int main(struct multiboot *mboot_ptr){  // Kernel Code.  return 0xDEADBABA;}<br />
common.c <br />functions for writing to and reading from the I/O bus, and some typedefs :<br />typedef unsigned int   u32i...
3. The Screen<br />3.1. The theory<br />Your kernel gets booted by GRUB in text mode.<br />That is, it has available to it...
3.1. The theory (Cont)<br />The framebuffer is just an array of 16-bit words, each 16-bit value representing the display o...
3.1. The theory (Cont)<br />
3.2.2. The monitor code<br />Moving the cursor<br />		static void move_cursor()<br />Scrolling the screen<br />		 static v...
3.2.2. The monitor code (Cont)<br />Writing a string<br />	void monitor_write(char *c){	   int i = 0;	   while (c[i])	   {...
The monitor<br />
4. The GDT and IDT<br />4.1. The Global Descriptor Table (theory)<br />	are arrays of flags and bit values describing the ...
GDT <br />there is one thing that segmentation can do that paging can't, and that's set the ring levels. <br />A ring is a...
The Global Descriptor Table (practical)<br />A GDT entry looks like<br />struct gdt_entry_struct{   u16int limit_low;     ...
GDT<br />u8int  access;  <br />  <br />P    Is segment present? (1 = Yes)DPL    Descriptor privilege level - Ring 0 - 3.DT...
GDT<br />To pass the GDT Table ,we pass the Pointer to this table to the CPU and pass its limit(length).<br />So we must u...
GDT<br />gdt_entry_t gdt_entries[5];gdt_ptr_t   gdt_ptr;<br />static void init_gdt(){   gdt_ptr.limit = (sizeof(gdt_entry_...
GDT<br />static void gdt_set_gate(s32int num, u32int base, u32int limit, u8int access, u8int gran){   gdt_entries[num].bas...
4.3. The Interrupt Descriptor Table (theory)<br />There are times when you want to interrupt the processor. You want to st...
Faults, traps and exceptions<br />The special, CPU-dedicated 32 interrupts :<br />0 - Division by zero exception<br />1 - ...
4.4. The Interrupt Descriptor Table (practice)<br />// A struct describing an interrupt gate.struct idt_entry_struct{   u1...
IDT<br />The DPL describes the privilege level we expect to be called from.<br />The P bit signifies the entry is present....
IDT<br />extern void isr0 ();...extern void isr31();<br />idt_entry_t idt_entries[256];idt_ptr_t   idt_ptr;<br />static vo...
IDT<br />[GLOBAL idt_flush]    ; Allows the C code to call idt_flush().idt_flush:   mov eax, [esp+4]  ; Get the pointer to...
IDT<br />Great! We've got code that will tell the CPU where to find our interrupt handlers - but we haven't written any ye...
interrupt.s<br />ISR_NOERRCODE 0ISR_NOERRCODE 1... <br />
interrupt.s<br />ASM common handler function to handle interrupts :<br />; In isr.c[EXTERN isr_handler]; This is our commo...
isr.c<br />typedef struct registers{   u32int ds;                  // Data segment selector   u32int edi, esi, ebp, esp, e...
Testing<br />// Kernel Code:<br />asm volatile ("int $0x3");asm volatile ("int $0x4"); <br />Output: <br />
Upcoming SlideShare
Loading in...5
×

Roll your own toy unix clone os

2,765

Published on

Roll your own toy unix clone os
by Ahmed Essam
Faculty of Computer and Information
Assiut University

Published in: Education
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
2,765
On Slideshare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
27
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Roll your own toy unix clone os

  1. 1. Roll your own toy UNIX-clone OS<br />By Ahmed Essam<br />www.eramax.org<br />
  2. 2. Table of contents<br /> 1. Environment setup2. Genesis3. The Screen4. The GDT and IDT<br />
  3. 3. Setting Environment<br />To compile and run the sample code we need to have <br /><ul><li>GCC
  4. 4. Ld
  5. 5. NASM
  6. 6. GNU Make</li></li></ul><li>Setting Environment (2)<br />To Run the OS we need a Virtual Machine Emulator such as <br /><ul><li>VirtualBox
  7. 7. Qemu
  8. 8. Bochs </li></li></ul><li>(boot loader)1. Environment setup<br />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.<br />What is GRUB<br />GNU GRUB is a Multiboot boot loader.<br /> 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 .<br />
  9. 9. (VM setup)1. Environment setup<br />We will use Bochs (an open-source x86-64 emulator).<br />We need a bochs configuration file (bochsrc.txt).<br />megs: 32floppya: 1_44="myOs2.bin", status=insertedboot: floppylog: bochsout.txtclock: sync=realtimecpu: ips=500000 <br />keyboard_paste_delay: 100000<br />
  10. 10. Useful scripts<br />Makefile<br /> making (compiling) our project.<br />Link.ld<br /> link files together into one ELF binary (Kernel).<br />update_image.sh<br /> poke your new kernel binary into the floppy image file.<br />run_bochs.sh<br /> mounts the correct loopback device, runs bochs, then unmounts.<br />
  11. 11. 2. Genesis2.1 - The boot code<br />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   <br />
  12. 12. 2.1 - The boot code (Cont)<br />  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 codestart:  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!<br />
  13. 13. 2.3. Adding some C code<br />// main.c int main(struct multiboot *mboot_ptr){  // Kernel Code.  return 0xDEADBABA;}<br />
  14. 14. common.c <br />functions for writing to and reading from the I/O bus, and some typedefs :<br />typedef unsigned int   u32int;typedef          int   s32int;typedef unsigned short u16int;typedef          short s16int;typedef unsigned char  u8int;typedef          char  s8int;<br />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;} <br />
  15. 15. 3. The Screen<br />3.1. The theory<br />Your kernel gets booted by GRUB in text mode.<br />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.<br />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.<br />
  16. 16. 3.1. The theory (Cont)<br />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:<br />(y * 80 + x) * 2 <br />8 bits are used to represent a character.<br />foreground and background colours (4 bits each).<br />
  17. 17. 3.1. The theory (Cont)<br />
  18. 18. 3.2.2. The monitor code<br />Moving the cursor<br /> static void move_cursor()<br />Scrolling the screen<br /> static void scroll()<br />Writing a character to the screen<br /> void monitor_put(char c)<br />location = video_memory + (cursor_y*80 + cursor_x);*location = c | attribute; <br />Clearing the screen<br /> u16int blank = 0x20 /* space */ | (attributeByte << 8);<br />   for (i = 0; i < 80*25; i++)    {         video_memory[i] = blank;    }<br />
  19. 19. 3.2.2. The monitor code (Cont)<br />Writing a string<br /> void monitor_write(char *c){    int i = 0;    while (c[i])    {        monitor_put(c[i++]);    }} <br />//-------- Kernel Code :<br /> monitor_clear();monitor_write("Hello, world!"); <br />
  20. 20. The monitor<br />
  21. 21. 4. The GDT and IDT<br />4.1. The Global Descriptor Table (theory)<br /> are arrays of flags and bit values describing the operation of the segmentation system.<br />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. <br />
  22. 22. GDT <br />there is one thing that segmentation can do that paging can't, and that's set the ring levels. <br />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.<br />A segment descriptor carries inside it a number representing the ring level it applies to. <br />
  23. 23. The Global Descriptor Table (practical)<br />A GDT entry looks like<br />struct 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));<br />
  24. 24. GDT<br />u8int  access;  <br />  <br />P    Is segment present? (1 = Yes)DPL    Descriptor privilege level - Ring 0 - 3.DT    Descriptor typeType    Segment type : code segment / data segment.<br />
  25. 25. GDT<br />To pass the GDT Table ,we pass the Pointer to this table to the CPU and pass its limit(length).<br />So we must use this struct :<br />struct gdt_ptr_struct{   u16int limit;               // The upper 16 bits of all selector limits.   u32int base;                // The address of the first gdt_entry_t }<br />
  26. 26. GDT<br />gdt_entry_t gdt_entries[5];gdt_ptr_t   gdt_ptr;<br />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);}<br />
  27. 27. GDT<br />static void gdt_set_gate(s32int num, u32int base, u32int limit, u8int access, 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;}<br />[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<br />
  28. 28. 4.3. The Interrupt Descriptor Table (theory)<br />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.<br />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. <br />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.<br /> 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.<br />
  29. 29. Faults, traps and exceptions<br />The special, CPU-dedicated 32 interrupts :<br />0 - Division by zero exception<br />1 - Debug exception<br />2 - Non maskable interrupt<br />3 - Breakpoint exception<br />4 - 'Into detected overflow'<br />5 - Out of bounds exception<br />6 - Invalid opcode exception<br />7 - No coprocessor exception<br />8 - Double fault (pushes an error code)<br />9 - Coprocessor segment overrun<br />10 - Bad TSS (pushes an error code)<br />11 - Segment not present (pushes an error code)<br />12 - Stack fault (pushes an error code)<br />13 - General protection fault (pushes an error code)<br />14 - Page fault (pushes an error code)<br />15 - Unknown interrupt exception<br />16 - Coprocessor fault<br />17 - Alignment check exception<br />18 - Machine check exception<br />19-31 - Reserved<br />
  30. 30. 4.4. The Interrupt Descriptor Table (practice)<br />// 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));<br />struct idt_ptr_struct{   u16int limit;   u32int base; // The address of the first element in our idt_entry_t array.} __attribute__((packed));<br />
  31. 31. IDT<br />The DPL describes the privilege level we expect to be called from.<br />The P bit signifies the entry is present.<br /> Any descriptor with this bit clear will cause a "Interrupt Not Handled" exception.<br />
  32. 32. IDT<br />extern void isr0 ();...extern void isr31();<br />idt_entry_t idt_entries[256];idt_ptr_t   idt_ptr;<br />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);<br />}<br />
  33. 33. IDT<br />[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<br />
  34. 34. IDT<br />Great! We've got code that will tell the CPU where to find our interrupt handlers - but we haven't written any yet! <br />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. <br />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. <br />
  35. 35. interrupt.s<br />ISR_NOERRCODE 0ISR_NOERRCODE 1... <br />
  36. 36. interrupt.s<br />ASM common handler function to handle interrupts :<br />; 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 <br />
  37. 37. isr.c<br />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; <br />// 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(' ');} <br />
  38. 38. Testing<br />// Kernel Code:<br />asm volatile ("int $0x3");asm volatile ("int $0x4"); <br />Output: <br />
  39. 39. References <br /><ul><li>http://www.jamesmolloy.co.uk
  40. 40. http://wiki.osdev.org/Main_Page
  41. 41. http://forum.osdev.org/
  42. 42. Intel® 64 and IA-32 Architectures Software Developer's Manuals http://www.intel.com/products/processor/manuals/
  43. 43. Memory Modes : Real Mode and Protected Mode</li></ul> http://3asfh.net/vb/showthread.php?t=37922<br />
  44. 44. References(2)<br />Making a Simple C kernel with Basic printf and clearscreen Functions<br />http://www.osdever.net/tutorials/basickernel.php<br />
  45. 45. Thank You<br />Thank You .<br />
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×