Build your own operating system #5_integrate_segmentation

Sadisha Nimsara
3 min readAug 13, 2021

In the previous article, we discussed how we can integrate outputs to the console and the serial port. In this article, we are going to integrate segmentation to our operating system.

So, let’s get started.

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. We can use a 48-bit logical address to address a byte in segmented memory. In that 48-bit logical address, 16 bits that specifies the segment and 32-bits that specifies what offset within that segment you want as you can see in the figure below(figure 1).

Figure 1

In order to enable segmentation, you need to setup a table to describe each of your segments. There are 2 types of descriptor tables in x86. They are Global Descriptor Table (GDT) and Local Descriptor Table (LDT). LDT is set up and managed by user-space processes, and all processes have their own LDT. LDT can be used for more complex segments. So we wont’ use it here. We will be using GDT. Because, the GDT is shared by everyone — it’s global.

Accessing the memory

Most of the time, you don’t need to explicitly specify the segment to use when accessing memory. Processors have 6 16-bit segment registers. Those are cs, ss, ds, es, gs, and fs. The register cs is the code segment register. It specifies the segment to use when fetching instructions. ss is the stack segment and it is used whenever accessing the stack (through the stack pointer esp), and ds is used for other data accesses. The OS is free to use the registers es, gs and fs however it want.

Create a new file called gdt_asm.s in your root directory of your project.

        mov eax, [esp+4]
mov ebx, [eax]
add ebx, 8
mov [eax], ebx
ret

And then, update the following in Makefile.(Add the gdt_asm.o to the OBJECTS)

OBJECTS = loader.o kmain.o io.o gdt_asm.o

Global Descriptor Table (GDT)

A GDT/LDT is an array of 8-byte segment descriptors. The first descriptor in the GDT is always a null descriptor and can never be used to access memory. At least two segment descriptors (plus the null descriptor) are needed for the GDT, because the descriptor contains more information than just the base and limit fields.

Loading the GDT

#ifndef INCLUDE_TYPES_H 
#define INCLUDE_TYPES_H
/* Typedefs, to standardise sizes across platforms.
* These typedefs are written for 32-bit X86.
*/
typedef unsigned int u32int;
typedef int s32int;
typedef unsigned short u16int;
typedef short s16int;
typedef unsigned char u8int;
typedef char s8int;
#endif /* INCLUDE_TYPES_H */

Save this file in a file called types.h in /drivers directory.

Save this file in the same directory as gdt.h. This file contains all the necessary codes to initialize the GDT. If the content of the eax register is the address to such a struct, then the GDT can be loaded with the assembly code shown below:

lgdt [eax]

Loading the segment selector registers is easy for the data registers — just copy the correct offsets to the registers:

mov ds, 0x10
mov ss, 0x10
mov es, 0x10
.
.
.

To load cs we have to do a “far jump”:

; code here uses the previous cs
jmp 0x08:flush_cs ; specify cs when jumping to flush_cs

flush_cs:
; now we've changed cs to 0x08

Now you can initialize the GDT in kmain.c with the following code:

#include "drivers/gdt.h"void init(){
init_gdt();
}
void kmain(){
init_gdt();
}

You can download a completed code that I have created for integrating segmentation for the OS from: here

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

Thank you!

--

--