WVB Operating System(Fourth Stage)

Warunajith Bandara
3 min readAug 13, 2021

--

Welcome back to my fourth article on operating system implementation. In my last article, I discussed how to display text on the console as well as writing data to the serial port. In this article, we are going to learn about Segmentation.

In operating systems, segmentation refers to the use of segments to access memory. Segments are overlapping parts of the address space defined by a base address and a limit. A 48-bit logical address is used to address a byte in segmented memory. The segment is specified by 16 bits out of 48, while the remaining 32 bits describe the offset inside that segment. The offset is added to the base address of the segment, and the resulting “linear address” is checked against the segment’s limit. If everything works out fine the result is a “linear address”.

To allow segmentation, you’ll need to create a segment descriptor table, which is a table that describes each segment. There are two types of descriptor tables which are the Global Descriptor Table (GDT) and Local Descriptor Tables (LDT). LDTs can be used if a more complex segmentation model is desired and the GDT is shared by everyone. We are using the GDT because it is global.

Accessing memory

When we are going to access the memory using segments, there is no need to explicitly specify the segment to use because the processor has six 16-bit segment registers called cs, ss, ds, es, gs, and fs and the register cs is the code segment register and specifies the segment to use when fetching instructions. The OS is free to use the other registers however it wants.

The below code shows explicit use of the segment registers.

func:
mov eax, [ss:esp+4]
mov ebx, [ds:eax]
add ebx, 8
mov [ds:eax], ebx
ret

The Global Descriptor Table

A GDT/LDT is an array of segment descriptors that are each 8 bytes long. The GDT’s initial descriptor is always null, and it can’t be used to access memory. More information is contained in the descriptor than only the base and limit fields. The “Type field” and the “Descriptor Privilege Level (DPL)” fields are the two most important fields for us.

It is not possible for a type field to be both readable and executable at the same time. As a result, two sections are required. one segment for executing code to be placed in “cs”, and another segment for reading and writing data to be placed in the other segment registers. The privilege levels required to use the section are specified in the DPL. x86 supports four privilege levels (PLs) ranging from 0 to 3, where PL0 is the most privileged. The kernel should be able to do anything, hence it utilizes kernel-mode segments with DPL set to 0. The current privilege level (CPL) is determined by the segment selector in “cs”.

Let,s look at how to load the GDT into the processor. It is done with the “lgdt” assembly code instruction, which takes the address of a struct that specifies the start and size of the GDT. After the GDT has been loaded the segment registers need to be loaded with their corresponding segment selectors. The below assembly code describes the loading GDT and segment selectors to the processor. This code needs to be saved as “gdt.s”.

Next, we are going to do the memory segmentation using “memory_segments.h” and “memory_segments.c” files like below.

Next, We need to call “segments_install_gdt” function in the “kmain.c” file. As well as we need to update our Makefile by adding “gdt.o” and “memory_segment.o” in order to compile.

Thank You!!!

References:

https://littleosbook.github.io/

--

--

Warunajith Bandara
Warunajith Bandara

Written by Warunajith Bandara

Associate Software Engineer(AI/ML) @ Eutech Cybernetics

No responses yet