Operating Systems, Segmentatiom

Hello Readers, Welcome back to another article in the Developing an Operating Systems series. Today I’m going to talk about segmentation which is an option in the x86 architecture to access memory using segments. In this article, I’m going to talk about segmentation and how to implant it into our OS.

Segmentation

In Operating Systems, Segmentation is a memory management technique in which the memory is divided into variable size parts. Each part is known as a segment that can be allocated to a process.

The details about each segment are stored in a table called a segment table. The segment table is stored in one (or many) of the segments.

The segment table contains mainly two information about segments. They are the Base which is the base address of the segment and the Limit which is the length of the segment. Segmentation divides the process into segments. Each segment contains the same type of functions such as the main function can be included in one segment and the library functions can be included in the other segment.

CPU generates a logical address that contains the Segment Number and theOffset

For Example:

Suppose a 16-bit address is used with 4 bits for the segment number and 12 bits for the segment offset so the maximum segment size is 4096 and the maximum number of segments that can be refered is 16.

When a program is loaded into memory, the segmentation system tries to locate space that is large enough to hold the first segment of the process, space information is obtained from the free list maintained by the memory manager. Then it tries to locate space for other segments. Once adequate space is located for all the segments, it loads them into their respective areas.

The operating system also generates a segment map table for each program.

With the help of segment map tables and hardware assistance, the operating system can easily translate a logical address into a physical address on the execution of a program.

The Segment number is mapped to the segment table. The limit of the respective segment is compared with the offset. If the offset is less than the limit then the address is valid otherwise it throws an error as the address is invalid.

In the case of valid addresses, the base address of the segment is added to the offset to get the physical address of the actual word in the main memory.

The above figure shows how to address translation is done in the case of segmentation.

Global Descriptor Table and Local Descriptor Table

Global Descriptor Table(GDT) is an array of 8-byte segment descriptors used by intel x86-family processors to define the characteristics of the various memory areas called segments used during program execution, including the base address, the size and access privileges like executability and writability.

The GDT can contain other segments as well. Every 8-byte entry in the GDT is a descriptor, these can be Task State Segment (TSS) descriptors, Local Descriptor Table (LDT) descriptors, or Call gate descriptors.

LDT is important when we separate the address space for multiple processes. There will be generally one LDT per user process that describes privately held memory. while GDT describes shared memory and kernel memory.

When any new process is created the operating system will create a new LDT. This new LDT will be in GDT. Sometimes LDTs are useful when we want to give read or write permissions from memory to any process. While memory separated through GDT will be visible for every process so every process can request for memory. But LDT gives the right to read or write for each process.

The DPL specifies the privilege levels required to use the segment. x86 allows for four privilege levels (PL), 0 to 3, where PL0 is the most privileged. In most operating systems (eg. Linux and Windows), only PL0 and PL3 are used. However, some operating systems, such as MINIX, make use of all levels. The kernel should be able to do anything, therefore it uses segments with DPL set to 0 (also called kernel mode). The current privilege level (CPL) is determined by the segment selector in cs.

In this OS I’m only going to use the GDT to get privilege levels

Accessing the memory

Most of the time when accessing memory there is no need to explicitly specify the segment to use. The processor has six 16-bit segment registers: cs, ss, ds, es, gs and fs. The register cs is the code segment register and specifies the segment to use when fetching instructions. The register ss 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 wants.

Below is an example showing implicit use of the segment registers:

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

The above example can be compared with the following one that makes explicit use of the segment registers:

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

You don’t need to use ss for storing the stack segment selector, or ds for the data segment selector. You could store the stack segment selector in ds and vice versa. However, in order to use the implicit style shown above, you must store the segment selectors in their indented registers.

My experiences in development

The first step in creating the GDT tables is to write the assembly function needed for it and then make it available in C. As we did earlier with in and out functions.

Assembly funstion to load the gdt tables

Then we’ll create a C header file containing the functions necessary to call this assembly function and configure the segments.

First, we’ll create the data structure needed to pass the data to the GDT. We’ll use the attribute ‘packed’ to tell GCC not to change any of the alignment in the structure during the compiling process.

Creating gdt Structures

Then we’ll create another function to set the GDT entry value.

Function to set gdt entry value

Finally, we’ll call the assembly function from the C code. The segments needed are described in the table below.

C function to intitialise gdt

After configuring the header is done we’ll call the init_gdt() function from our kmain.c file.

Calling gdt initialise function from kmain

After they are all done add the location of the gdt.o file to the make file objects and we are done.

Object line of the makefile

References

References

Helin E, Renberg A. (2015). The little book about OS development: https://littleosbook.github.io/

Javapoint (2019). Segmentation: https://www.javatpoint.com/os-segmentation

Thank you for reading and I will be back next week with the next article.

Software Engineering Undergraduate of University of Kelaniya, Sri Lanka.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

How to contribute to WSO2 API Manager?

Experience with getting started to Software Development Life Cycle:)

How to make a twitter bot with no coding required in 5 minutes

Pushing the limits of CloudFormation inline Lambda to the max

Features in online classified script

Elixir OTP and CockroachDB

Foundations in Azure DevOps YAML pipelines

Using Google Analytics 4, Google Ads, and Google Tag Manager

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Mahela Dissanayake

Mahela Dissanayake

Software Engineering Undergraduate of University of Kelaniya, Sri Lanka.

More from Medium

Understand Kafka-Python.

Kafka Architecture

Save time by building a continuous training pipeline for ML. Part 1

Schema naming strategies in Confluent Schema Registry

Scheduling of industrial processes