#4-integrate_segmentation

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.

dIn this week we are going to work with memory managment process of our operating system. Memory management is the function in charge of managing the primary memory(RAM) of a computer. Before we need to have a clear idea about two concepts which are called as Paging and Segmentation.

What is Paging?

Paging is a technique for allocating non-contiguous memory. It is a partitioning theme with a fixed size . Both main memory and secondary memory are split into equal fixed size divisions during paging. Pages and frames are the names given to the divisions of the secondary memory area unit and the main memory area unit, respectively.

In paging, each process is divided into sections with the sizes of sections that are same as the page size.

Logical memory is a correlation between physical memory of the computer system and an address range that is accessible to devices. In a virtual memory system, it may be in main memory or secondary storage. Physical memory refers to the actual RAM of the system.

What is Segmentation?

Segmentation in operating systems means accessing the memory through segments. Segments are chunks of address space defined by a base address and a limit.

To address a byte in segmented memory, you use a 48-bit logical address: 16 bits for the segment and 32 bits for the offset within that segment (See the figure below).

Bit:     | 48 47 46... 18 17 16| 15 14 13 ... 2 1 0 |
Content: | offset | base address |

The offset is added to the segment’s base address, and the resulting linear address is compared to the segment’s limit.

As a result, we have a linear address.

When paging is disabled, the linear address space is mapped 1:1 onto the physical address space, allowing access to physical memory.

You must configure the table describing each segment to allow segmentation which is called as a segment descriptor table. Two types of tables are available in x86: the Global Descriptor Table (GDT) and the Local Descriptor Tables (LDT).

Global Descriptor Table (GDT)

A number of items named the Segment Descriptors are included in the GDT table. Each one contains 8 bytes and provides information about the segment start-point, the length of the segment and the segment’s access privileges.

The following NASM-syntax code represents a single GDT entry:

struc gdt_entry_struct	limit_low:   resb 2
base_low: resb 2
base_middle: resb 1
access: resb 1
granularity: resb 1
base_high: resb 1
endstruc

Let’s discuss about this with more details later.

Local Descriptor Table (LDT)

A number of different memory segments will be used in each individual program from the operating system. In this local descriptor table, the features of each local memory segment are saved. The GDT contains pointers to each LDT. An LDT is established and controlled by user-space processes and has their own LDT in all processes.

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.

After completing all the above steps, you can run your OS with the same command as before (make run). However, Since this is an internal change, you won’t be able to notice any change in the operating system. If the code runs without errors, that guarantees that you have done the job correctly.

References:

Thank you very much for reading!

Hope to see you again with “interupt_and_inputs” chapter. Till then, STAY SAFE!!!

Happy reading …

-Thushara Samaraweera-

--

--