Build your own operating system

Nirmal Kapilarathne
4 min readAug 13, 2021

Part 04

Hello Everyone !!

This is the fourth article in this series. In this article, I’m going to talk about how to integrate segmentation. In the previous article, I explained how to integrate outputs.For those coding and implementation staff, I’m following the guide “The little book about OS development” by Erik Helin and Adam Renberg.

So let’s begin…

First of all let’s see What is Segmentation ?

Segmentation in x86 means accessing the memory through segments. Segments are portions of the address space, possibly overlapping, specified by a base address and a limit.

To address a byte in segmented memory you use a 48-bit logical address: 16 bits that specifies the segment and 32-bits that specifies what offset within that segment you want.

To enable segmentation you need to set up a table that describes each segment a segment descriptor table. In x86, there are two types of descriptor tables: the Global Descriptor Table (GDT) and Local Descriptor Tables (LDT).

First you should create a header file called memory_segment.h .Inside the file store the following code.

#ifndef INCLUDE_MEMORY_SEGMENTS
#define INCLUDE_MEMORY_SEGMENTS
struct GDT
{
unsigned short size;
unsigned int address;
} __attribute__((packed));
struct GDTDescriptor
{
unsigned short limit_low;
unsigned short base_low;
unsigned char base_middle;
unsigned char access_byte;
unsigned char limit_and_flags;
unsigned char base_high;
} __attribute__((packed));
void segments_init_descriptor(int index, unsigned int base_address, unsigned int limit, unsigned char access_byte, unsigned char flags);
void segments_install_gdt();
// Wrappers around ASM.
void segments_load_gdt(struct GDT gdt);
void segments_load_registers();
#endif /* INCLUDE_MEMORY_SEGMENTS */

Now you have to create a c file and include the above header file.

So you have to create a file called memory_segments.c .Then store the following code segment inside the file.

#include "memory_segments.h"#define SEGMENT_DESCRIPTOR_COUNT 3#define SEGMENT_BASE 0
#define SEGMENT_LIMIT 0xFFFFF
#define SEGMENT_CODE_TYPE 0x9A
#define SEGMENT_DATA_TYPE 0x92
/*
* Flags part of `limit_and_flags`.
* 1100
* 0 - Available for system use
* 0 - Long mode
* 1 - Size (0 for 16-bit, 1 for 32)
* 1 - Granularity (0 for 1B - 1MB, 1 for 4KB - 4GB)
*/
#define SEGMENT_FLAGS_PART 0x0C
static struct GDTDescriptor gdt_descriptors[SEGMENT_DESCRIPTOR_COUNT];void segments_init_descriptor(int index, unsigned int base_address, unsigned int limit, unsigned char access_byte, unsigned char flags)
{
gdt_descriptors[index].base_low = base_address & 0xFFFF;
gdt_descriptors[index].base_middle = (base_address >> 16) & 0xFF;
gdt_descriptors[index].base_high = (base_address >> 24) & 0xFF;
gdt_descriptors[index].limit_low = limit & 0xFFFF;
gdt_descriptors[index].limit_and_flags = (limit >> 16) & 0xF;
gdt_descriptors[index].limit_and_flags |= (flags << 4) & 0xF0;
gdt_descriptors[index].access_byte = access_byte;
}
void segments_install_gdt()
{
gdt_descriptors[0].base_low = 0;
gdt_descriptors[0].base_middle = 0;
gdt_descriptors[0].base_high = 0;
gdt_descriptors[0].limit_low = 0;
gdt_descriptors[0].access_byte = 0;
gdt_descriptors[0].limit_and_flags = 0;
// The null descriptor which is never referenced by the processor.
// Certain emulators, like Bochs, will complain about limit exceptions if you do not have one present.
// Some use this descriptor to store a pointer to the GDT itself (to use with the LGDT instruction).
// The null descriptor is 8 bytes wide and the pointer is 6 bytes wide so it might just be the perfect place for this.
// From: http://wiki.osdev.org/GDT_Tutorial
struct GDT* gdt_ptr = (struct GDT*)gdt_descriptors;
gdt_ptr->address = (unsigned int)gdt_descriptors;
gdt_ptr->size = (sizeof(struct GDTDescriptor) * SEGMENT_DESCRIPTOR_COUNT) - 1;
// See http://wiki.osdev.org/GDT_Tutorial
segments_init_descriptor(1, SEGMENT_BASE, SEGMENT_LIMIT, SEGMENT_CODE_TYPE, SEGMENT_FLAGS_PART);
segments_init_descriptor(2, SEGMENT_BASE, SEGMENT_LIMIT, SEGMENT_DATA_TYPE, SEGMENT_FLAGS_PART);
segments_load_gdt(*gdt_ptr);
segments_load_registers();
}

Now we should create a source file in order to load the Global descriptor table. Use the following code to the source file.

;Load the Global Descriptor Tableglobal segments_load_gdt
global segments_load_registers
segments_load_gdt:
lgdt [esp + 4]
ret
segments_load_registers:
mov ax, 0x10
mov ds, ax ; 0x10 - an offset into GDT for the third (kernel data segment) record.
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
jmp 0x08:flush_cs ; 0x08 - an offset into GDT for the second (kernel code segment) record.
flush_cs:
ret

Now that our GDT loading infrastructure is in place, and we compile and link it into our kernel, we need to call,

segments_install_gdt();

in order to actually do our work!

Open ‘kmain.c’ and add ‘segments_install_gdt();’ as the very first line in your main() function. The GDT needs to be one of the very first things that you initialize because as you learned from this section of the tutorial, it’s very important. You can now compile, link, and send our kernel to our floppy disk to test it out. You won’t see any visible changes on the screen: this is an internal change.

Hope you have successfully integrated outputs to your OS and hope to catch you in the next article.

Thank you…

--

--