YmeOS — Let’s Build our own OS !!!😉

Thushara Samaraweera
5 min readSep 6, 2021

#7-virtual memory paging

Hello readers !!!

Previous article onwards we are walking in the path to execute a program in user mode. Welcome back to the 7th part of my OS developing article series. In this article, I will explain to you about virtual Memory and Paging.

“Virtual memory” is an abstraction of physical memory. The purpose of virtual memory is generally to simplify application development and to let processes address more memory than what is actually physically present in the machine. We also don’t want applications messing with the kernel or other applications’ memory due to security.

In the x86 architecture, virtual memory can be accomplished in two ways:

  1. Paging is a computer memory management function that presents storage locations to the computer’s CPU as additional memory, called virtual memory.
  2. Segmentation is a virtual process that creates variable-sized address spaces in computer storage for related data, called segments.

Why Paging?

Paging is the most common technique used in x86 to enable virtual memory. Virtual memory through paging means that each process will get the impression that the available memory range is 0x00000000 - 0xFFFFFFFF even though the actual size of the memory might be much less.

Paging is optional, and some operating systems do not make use of it. But if we want to mark certain areas of memory accessible only to code running at a certain privilege level (to be able to have processes running at different privilege levels), paging is the neatest way to do it.

Paging in x86

Paging in x86 consists of a page directory (PDT) that can contain references to 1024 page tables (PT), each of which can point to 1024 sections of physical memory called page frames (PF). Each page frame is 4096 byte large. In a virtual (linear) address, the highest 10 bits specifies the offset of a page directory entry (PDE) in the current PDT, the next 10 bits the offset of a page table entry (PTE) within the page table pointed to by that PDE. The lowest 12 bits in the address is the offset within the page frame to be addressed.

Identity Paging

This is the simplest kind of paging. Identity Paging, Identity Mapped Paging and 1:1 Paging are terms often used for mapping virtual addresses to physical addresses that have the same value. This means that if paging is enabled with identity paging, 0xb8000 is 0xb8000. Identity paging can be done at compile time by creating a page directory where each entry points to its corresponding 4 MB frame.

Enabling Paging

Create a file called paging_enable.s and include following assembly code to it.

Now create a file called paging.h

paging.c file:

Paging and the Kernel

Noe let’s see how this paging effects the heart of OS, the Kernel.

Generally, during the linking process, the linker assumes that the code will be loaded into the memory position 0x00000000. Therefore, when resolving absolute references, 0x00000000 will be the base address for calculating the exact position. But if the kernel is mapped on the beginning of the virtual address space (0x00000000, “Size of kernel”), the user mode process cannot be loaded at virtual address 0x00000000. Therefore, the assumption from the linker that the user mode process is loaded into memory at position 0x00000000 becomes wrong. Although this can be corrected by using a linker script which tells the linker to assume a different starting address, but it will be unmanageable for the users of the operating system.

Higher-half Linker Script

For this we have to modify the link.ld to implement this. The following code can be used:

Entering the Higher Half

When GRUB jumps to the kernel code, there will be no paging table. Therefore, all references to 0xC0100000 + x won’t be mapped to the correct physical address, and will therefore cause a general protection exception or the computer will just crash.

Therefore, assembly code that doesn’t use relative jumps or relative memory addressing must be used to do the following:

  • Set up a page table.
  • Add identity mapping for the first 4 MB of the virtual address space.
  • Add an entry for 0xC0100000 that maps to 0x0010000

If we skip the identity mapping for the first 4 MB, the CPU would generate a page fault immediately after paging was enabled when trying to fetch the next instruction from memory. After the table has been created, an jump can be done to a label to make eip point to a virtual address in the higher half:

; assembly code executing at around 0x00100000
; enable paging for both actual location of kernel
; and its higher-half virtual location lea ebx, [higher_half] ; load the address of the label in ebx
jmp ebx ; jump to the label higher_half:
; code here executes in the higher half kernel
; eip is larger than 0xC0000000
; can continue kernel initialisation, calling C code, etc.

The register eip will now point to a memory location somewhere right after 0xC0100000 - all the code can now execute as if it were located at 0xC0100000, the higher-half. The entry mapping of the first 4 MB of virtual memory to the first 4 MB of physical memory can now be removed from the page table and its corresponding entry in the TLB invalidated with invlpg [0].

All files look like this:

Now type ‘ make run ’on terminal,

Then you may see

References:

Thank you very much for reading!

Hope to see you again with next chapter. Till then, STAY SAFE!!!

Happy reading …

-Thushara Samaraweera-

--

--