Comments

I have re-enabled comments. not that there is much to comment on. It is ugly but if and when I update my website I will fix that. there is a very simple image validation thingy at the bottom of the commenter box.

Me brain no normal be

Your result for The Right Brain vs Left Brain Test…

Center Brained!

Much like Goldilocks, you have gone the middle way.

Slighlty less typical, you could consider yourself center brained. Center brained people are neither left nor right dominant, but rather exhibit equal qualities of both. If you are center brained, you are likely both logical and creative, excel in the maths and sciences, and also art and philosophy.

Take The Right Brain vs Left Brain Test at HelloQuizzy

Care and Feeding of the IA-32. Part 1

 Introduction:

This instalment of build your own OS is going to continue where we left off in the first one. If you have not yet read it, I would recommend it.

When we finished last time, we had a very small kernel that booted up and displayed "Hello World" at the top of the screen. This time we are going to build some helpful libraries that we will use in our kernel as well as set up some of the basic data structures for the processor.

There are three basic modules we will create. The first being one to facilitate writing to the screen. The second module will initialize the basic memory structures the processor needs to run. The third and final module we will write this time will be our general purpose interrupt handler.

We still have a long way to go before we can call this a true operating system, but we will be closer.

Writing To The Screen:

In the very least what we would want when we write to the screen is something that would take a string and place it in memory. We would need it to recognize carriage returns as well as tabs. When it reaches the end of the screen it would be nice if it would also wrap.

If we wanted to get fancy it would also be nice to be able to set the background and foreground colour, and to move the current writing position anywhere on the screen we wanted.

In reality this is very easy. As we already know, the text mode screen buffer is located at 0xB8000 and contains two bytes the first of which is the character and the second the foreground/background colour. Remember that this is little endian so if we are working with 16 bit integers the first byte (high byte) is the colour and the low byte is the character.

The screen is by default 80 characters wide by 25 lines, so we have a 4000 byte buffer to deal with.

Printing is fairly simple. All we need to do is loop through all characters in the string and set the current position in the buffer to that character. In order to to this we need to keep track of our position in the buffer and increment with each character we output. When we reach the end of the buffer we need to copy the last 24 lines (3840 bytes) to the start of the buffer and set the current position to the start of the last line (byte 3840).

I also stole code to enable/disable the blinking cursor and to move it around the screen. As we can see below in the code segment, I the cursor does not move if disabled. This is because moving the cursor is very expensive and as such should not be done unless we need to.

I wrote three helper modules, the first of which helps with manipulating memory. I copied the signatures from the ANSI c documentation in order to reduce confusion. The second is for string manipulation. The third contains some typedefs so that the size of the integers are explicit.

The following is my full implementation of the text only screen buffer manipulation module. This may have to change if I want to use the same module for graphics mode text. At that point I probably will be wishing I was using C++.

screen.c:

#include "memory.h" #include "string.h" #include "common.h" #include "screen.h"  // we need to keep track of some values // including the current x,y position on the screen // and the current foreground and background  unsigned char colour; sint_16 currentX; sint_16 currentY;  unsigned char width; unsigned char height;  uint_16 m_crtc_adr;// = 0x3D4; /* 0x3B4 for monochrome */  // cursor disabled/enabled flag uint_8 m_cursor;  // the screen buffer uint_16 *buffer;  // private declarations void printChar(const char c); void incrementPosition(sint_16 count); void scroll(); void printCharacter(const char c); void printBaseNumber(uint_32 number, uint_32 base); void moveCursor(uint_8 x, uint_8 y); unsigned char inportb (unsigned short _port); void outportb (unsigned short _port, unsigned char _data);  // the functions / methods we will be using void initScreen() { 	buffer = (uint_16*)0xB8000; 	width = 80; 	height = 25; 	colour = 0x07;	// light grey on black 	m_crtc_adr = 0x3D4; 	enableCursor(0); 	cls(); }  // clears screen sets cursor to top of screen void cls() { 	memsetw((void *)buffer, (colour << 8) | 0x20 , width * height);  	setPosition(0,0); }  // prints a null terminating string  void printString(const char * string) { 	uint_16 length; 	uint_16 i; 	 	length = strlen(string); 	 	for (i = 0; i < length; i++) 	{ 		printChar(*(string + i)); 	}		 }  // sets the current cursor position void setPosition(uint_8 x, uint_8 y) { 	currentX = 0; 	currentY = 0; 	incrementPosition(width * y + x); }  // returns current x position int getPositionX() { 	return currentX;	 }  // returns current y position int getPositionY() { 	return currentY; }  // sets the current text colour void setForeColour(int c) { 	colour = (colour & 0xf0) | (c & 0xf); }  // sets current background colour void setBackColour(int c) { 	colour = (colour & 0xf) | ((c & 0xf) << 4); }  // this should be in a math module uint_32 pow(uint_32 base, uint_32 exponent) { 	uint_32 i, ret; 	ret = 1; 	for (i =0; i < exponent; i++) 	{ 		ret*=base; 	} 	return ret; }  // prints a hex string to screen void printHex(uint_32 number) { 	printBaseNumber(number, 16); }  // pri ts binary string to screen void printBinary(uint_32 number) { 	printBaseNumber(number, 2); }  // prints a base 10 number to screen void printNumber(uint_32 number) { 	printBaseNumber(number, 10); }  // displays, or hides the cursor void enableCursor(uint_8 b) { 	m_cursor = b; 	if (b == 0) 	{ 		outportb(0x3d4, 0x0a); 		outportb(0x3d5, 0x2f); 		outportb(0x3d4, 0x0b); 		outportb(0x3d5, 0x0f); 	} else { 		outportb(0x3d5, 0x0a); 		outportb(0x3d5, 0x2f); 		outportb(0x3d4, 0x0b); 		outportb(0x3d5, 0x0f); 		moveCursor(currentX, currentY); 	} }  // the following are helpers and should not be exposed  // prints a single character to screen void printChar(const char c) { 	// we need to handle some special chars 	 	if (c == 8)	// backsp 	{ 		incrementPosition(-1);		 		printCharacter(c);		 	} else if (c == 'n') {	// enter char (ish)	 		incrementPosition(width - currentX);	 	} else if (c == 't') {	// tab 		// 8 char tabs 		printChar(' '); 		while((currentX  % 8)  > 0) printChar(' '); 	} else if (c == 'r') { // cr 		incrementPosition(-1 * currentX); 	} else { 		printCharacter(c); 		incrementPosition(1); 	} }  // moves the cursor realative to current position void incrementPosition(sint_16 count) { 	int x, y; 	 	count += currentX; 	x = count % width; 	y = (count - x) / width; 	 	currentX = x; 	currentY += y; 	 	if (currentY >= height) scroll(); 	else if (currentY < 0) { currentY = 0; currentX = 0;} 	 	moveCursor(currentX, currentY); }  // scrolls the screen, moves data up void scroll() { 	int lines; 	 	lines = (currentY - height + 1); 	 	if (lines > 0 && lines < height) 	{ 		memcpy(buffer, buffer + (lines * width), ((height - lines) * width * 2)); 		memsetw(buffer + ((height - lines) * width), (colour << 8) | 0x20, lines * width); 		currentY = height -1; 	} else if (lines > 0) { 		cls(); 		currentY = height -1; 	} }  // sets a single character on screen void printCharacter(const char c) { 	*(buffer + (width * currentY) + currentX) = (colour << 8) | c ; }  /* We can use this for reading from the I/O ports to get data from *  devices such as the keyboard. We are using what is called 'inline *  assembly' in these routines to actually do the work. [XXX I still *  have to add devices to the tutorial] */ unsigned char inportb (unsigned short _port) {     unsigned char rv;     __asm__ __volatile__ ("inb %1, %0" : "=a" (rv) : "dN" (_port));     return rv; }  /* We can use this to write to I/O ports to send bytes to *  devices. Again, we use some inline assembly for the stuff that *  simply cannot be done in C */ void outportb (unsigned short _port, unsigned char _data) {     __asm__ __volatile__ ("outb %1, %0" : : "dN" (_port), "a" (_data)); }  // moves the cursor void moveCursor(uint_8 x, uint_8 y) { 	if (!m_cursor) return; 	 	uint_16 offset = x + y * width;  	outportb(m_crtc_adr + 0, 14);     /* MSB of offset to CRTC reg 14 */ 	outportb(m_crtc_adr + 1, offset >> 8); 	outportb(m_crtc_adr + 0, 15);     /* LSB of offset to CRTC reg 15 */ 	outportb(m_crtc_adr + 1, offset); }  // the workhorse behind the number printing void printBaseNumber(uint_32 number, uint_32 base) { 	uint_32 num = number; 	uint_8 count = 0; 	const char* chars = "0123456789abcdefghijklmnopqrstuvwxyz"; 	 	if (number == 0) 	{ 		printChar('0'); 	} else { 		while (num > 0) 		{ 			count++; 			num /= base; 		} 		while (count > 0) 		{ 			count--; 			printChar(chars[((number / pow(base, count)) % base)]);			 		} 	} }

screen.h:

#pragma once	 #include "common.h"  void cls(); void initScreen(); void printString(const char * string); void setPosition(uint_8 x, uint_8 y); int getPositionX(); int getPositionY(); void setForeColour(int c); void setBackColour(int c); void printNumber(uint_32 number); void printHex(uint_32 number); void printBinary(uint_32 number); void enableCursor(uint_8 b);

At this point we will be able to use PrintNumber(123) and PrintString("this is some textn") to format our output on the screen. For a majority of what we will be doing, that should be enough.

Configuring Our Memory Segments:

We will not be using segments. Well not as our main memory model. Later on we will configure paging but for now the processor requires that segments are configured. Grub may have already configured something but it is possible we will overwrite it later on, so it is best to simply initialize them to something that the processor will be happy with then forget it.

We really don’t need to worry about segmentation, or what it does. But suffice to say it segments our memory into chunks and assigns permissions to these chunks. For ease and simplicity we will configure four segments and point them all to the same chunk of physical memory. The code for that is below. It creates the user data and code segments as well as the system data and code segments.

segments.c:

#include "segments.h" #include "memory.h" #include "screen.h"  gdt_entry_t gdt_entries[5]; gdt_ptr_t   gdt_ptr;   // forward declarations for methods hidden from user. (me!) void loadSegments(gdt_ptr_t *address); void createSegment(gdt_entry_t *gdt, uint_8 ring, uint_8 type);  // initializes the segments void initSegments() { 	// initialize the location and number of entries 	gdt_ptr.limit = (sizeof(gdt_entry_t) * 5) - 1; 	gdt_ptr.base  = (uint_32)&gdt_entries;  	// create each entry 	memset(&gdt_entries,0,sizeof(gdt_entry_t));		// null 	createSegment(&gdt_entries[1],0,10);		// os code 	createSegment(&gdt_entries[2],0,2);		// os data 	createSegment(&gdt_entries[3],3,10);		// user code 	createSegment(&gdt_entries[4],3,2);		// user data 	 	// tell the processor about them 	loadSegments(&gdt_ptr); }  // properly fill a segment descriptor void createSegment(gdt_entry_t *gdt, uint_8 ring, uint_8 type) { 	// for each segment we will start at 0 	gdt->base_low = 0; 	gdt->base_middle = 0; 	gdt->base_high   = 0;  	// and extend to 4GB 	gdt->limit_low   = 0xFFFF; 	gdt->granularity = 0xCF; 	 	// all we really need to set is the type (code/data) and permissions 	gdt->access = 0x90 | type | (ring << 5); }  // tell the processor about the new segments void loadSegments(gdt_ptr_t *address) {   	//mov eax, [esp+4]  ; Get the pointer to the GDT, passed as a parameter. 	 	asm volatile ("lgdt (%0)nt"			//   lgdt [eax]        ; Load the new GDT pointer 				  "movw $0x10, %%axnt"	//   mov ax, 0x10      ; 0x10 is the offset in the GDT to our data segment 				  "movw %%ax, %%dsnt"		//   mov ds, ax        ; Load all data segment selectors 				  "movw %%ax, %%esnt"		//   mov es, ax 				  "movw %%ax, %%fsnt"		//   mov fs, ax 				  "movw %%ax, %%gsnt"		//   mov gs, ax 				  "movw %%ax, %%ssnt"		//   mov ss, ax 				  "ljmp $0x08,$flushn"		//   jmp 0x08:.flush   ; 0x08 is the offset to our code segment: Far jump! 				  "flush:"					//.flush 				  :: "r" (address) 			//ret     	); 	 }

segments.h:

#pragma once #include "common.h"  void initSegments();  struct gdt_ptr_struct {    uint_16 limit;               // The upper 16 bits of all selector limits.    uint_32 base;                // The address of the first gdt_entry_t struct. } __attribute__((packed)); typedef struct gdt_ptr_struct gdt_ptr_t;   // This structure contains the value of one GDT entry. // We use the attribute 'packed' to tell GCC not to change // any of the alignment in the structure. struct gdt_entry_struct {    uint_16 limit_low;           // The lower 16 bits of the limit.    uint_16 base_low;            // The lower 16 bits of the base.    uint_8  base_middle;         // The next 8 bits of the base.    uint_8  access;              // Access flags, determine what ring this segment can be used in.    uint_8  granularity;    uint_8  base_high;           // The last 8 bits of the base. } __attribute__((packed)); typedef struct gdt_entry_struct gdt_entry_t;

A simple call to initSegments() will configure the segments. At that point we can ignore them.

Creating A Generic Interrupt Handler:

Most everything that happens on a computer involves an Interrupt of some sort. When the processor encounters an error, such as invalid memory or instruction, or when the keyboard has new data and Interrupt is sent to the processor. We as the OS developer have it in our power to capture these interrupts and deal with them accordingly. At this point we won’t be doing much with them other than printing to the screen that we received one. The design that I have below has one generic handler for all 256 interrupts. At some point in time I will add the methods and data structures that will enable us to register specific interrupt handlers for an interrupt.

In order to receive the interrupts we however first need a low level handler for each interrupt. This means we need 256 small methods to call our one main handler. This is very tedious as one would imagine. I found this method in another tutorial online. Because some Interrupts have an error code (mainly some exceptions) we need to create two stubs one which creates a blank error so that we can use a generic handler. The main common stub throws everything on the stack and then calls our main handler for distribution.

I have truncated this file for space requirements. The full file is available in the download at the bottom.

interrupts.asm:

%macro ISR_NOERRCODE 1  ; define a macro, taking one parameter   [GLOBAL isr%1]        ; %1 accesses the first parameter.   isr%1:     cli     ;push byte 0     ;push byte %1 	push 0 	push %1     jmp isr_common_stub %endmacro  %macro ISR_ERRCODE 1   [GLOBAL isr%1]   isr%1:     cli     ;push byte %1 	push %1     jmp isr_common_stub %endmacro   [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   ISR_NOERRCODE 0 ISR_NOERRCODE 1 ISR_NOERRCODE 2 ISR_NOERRCODE 3 ISR_NOERRCODE 4 ISR_NOERRCODE 5 ISR_NOERRCODE 6 ISR_NOERRCODE 7 ISR_ERRCODE 8 ISR_NOERRCODE 9 ISR_ERRCODE 10 ISR_ERRCODE 11 ISR_ERRCODE 12 ISR_ERRCODE 13 ISR_ERRCODE 14 ISR_NOERRCODE 15  ...  ISR_NOERRCODE 255

As we can see the above code requires the existence of an external method called isr_handler. This method as well as the methods for initializing the interrupts are in interrupts.c.

Configuring Interrupts are relatively easy although tedious as well. All we need to do is declare and configure the idt_entries structure for each interrupt. The only needed information is the address of the code we wish to execute (the stub in assembler above) and the permissions.

interrupts.c:

#include "interrupts.h" #include "memory.h" #include "screen.h"  idt_entry_t idt_entries[256]; idt_ptr_t   idt_ptr;  extern void isr0(); extern void isr1(); extern void isr2(); extern void isr3(); extern void isr4(); extern void isr5(); extern void isr6(); extern void isr7(); extern void isr8(); extern void isr9(); extern void isr10(); extern void isr11(); extern void isr12(); extern void isr13(); extern void isr14(); extern void isr15();    void createInterrupt(uint_8 num, uint_32 address); void loadInterrupts(idt_ptr_t *address);  void initInterrupts() { 	 	idt_ptr.limit = sizeof(idt_entry_t) * 256 -1; 	idt_ptr.base  = (uint_32)&idt_entries; 	 	memset(&idt_entries, 0, sizeof(idt_entry_t) * 256);  	uint_16 i; 	 	createInterrupt(0, (uint_32)isr0); 	createInterrupt(1, (uint_32)isr1); 	createInterrupt(2, (uint_32)isr2); 	createInterrupt(3, (uint_32)isr3); 	createInterrupt(4, (uint_32)isr4); 	createInterrupt(5, (uint_32)isr5); 	createInterrupt(6, (uint_32)isr6); 	createInterrupt(7, (uint_32)isr7); 	createInterrupt(8, (uint_32)isr8); 	createInterrupt(9, (uint_32)isr9); 	createInterrupt(10, (uint_32)isr10);	       	createInterrupt(11, (uint_32)isr10);	       	createInterrupt(12, (uint_32)isr10);	       	createInterrupt(13, (uint_32)isr10);	       	createInterrupt(14, (uint_32)isr10);	       	createInterrupt(15, (uint_32)isr10);	        	for (i = 15; i < 256; i++) 	{ 		createInterrupt(i, (uint_32)isr15 + ((i - 15) * 16)); 	}  	loadInterrupts(&idt_ptr); }  void createInterrupt(uint_8 num, uint_32 address) { 	idt_entries[num].base_lo = address & 0xFFFF; 	idt_entries[num].base_hi = (address >> 16) & 0xFFFF; 	 	idt_entries[num].sel     = 0x08; 	idt_entries[num].always0 = 0;  	idt_entries[num].flags  = 0xEE; }  void loadInterrupts(idt_ptr_t *address) { 	asm volatile ("lidt (%0)nt"			//   lidt [eax]        ; Load the new IDT pointer 				  :: "r" (address)); }  // This gets called from our ASM interrupt handler stub. void isr_handler(registers_t regs) {    printString("recieved interrupt: ");    printNumber(regs.int_no);    printString("n"); } 

interrupts.h:

#pragma once #include "common.h"  void initInterrupts();  // A struct describing an interrupt gate. struct idt_entry_struct {    uint_16 base_lo;             // The lower 16 bits of the address to jump to when this interrupt fires.    uint_16 sel;                 // Kernel segment selector.    uint_8  always0;             // This must always be zero.    uint_8  flags;               // More flags. See documentation.    uint_16 base_hi;             // The upper 16 bits of the address to jump to. } __attribute__((packed)); typedef struct idt_entry_struct idt_entry_t;  // A struct describing a pointer to an array of interrupt handlers. // This is in a format suitable for giving to 'lidt'. struct idt_ptr_struct {    uint_16 limit;    uint_32 base;                // The address of the first element in our idt_entry_t array. } __attribute__((packed)); typedef struct idt_ptr_struct idt_ptr_t;  typedef struct registers {    uint_32 ds;                  // Data segment selector    uint_32 edi, esi, ebp, esp, ebx, edx, ecx, eax; // Pushed by pusha.    uint_32 int_no, err_code;    // Interrupt number and error code (if applicable)    uint_32 eip, cs, eflags, useresp, ss; // Pushed by the processor automatically. } registers_t; 

To initialize the interrupts, all we need to do is call initInterrupts(). At this point nothing much is done with the interrupt, but gives us a firm starting point for more advanced things like exception handling, hardware manipulation, and multitasking.

Putting Some of It Together:

Because of the sudden increase in our number of modules we must modify our make file. The updated file is included in the source file. As you may notice I also added a parameter to the main function which references a multiboot structure. In order for this to work we must also add a push statement to our main assembler start-up routine that pushes the address of this multiboot data to the stack. This multiboot data is part of the GNU multiboot specifications. Grub gathers this information for us so all we have to do is reference it. It contains information on the hardware and memory which will come in handy later on.

Source:

MOS http://downloads.terdos.com/mos/mos2.zip
Grub Boot Disk http://downloads.terdos.com/mos/grubboot.zip

References:

Writing Kernel Tutorial: http://www.cs.vu.nl/~herbertb/misc/writingkernels.txt
The GDT and IDT: http://www.jamesmolloy.co.uk/tutorial_html/4.-The%20GDT%20and%20IDT.html

Further Reading:

OS Dev Wiki: http://wiki.osdev.org 
OS Dev Forum: http://forum.osdev.org/
Bona Fide OS Development News: http://www.osdever.net/