Build your own operating system

Nirmal Kapilarathne
4 min readSep 10, 2021

Part 08

Hello everyone…

This is the last part of my Operating System article series. Last week we allocated a virtual memory to the Operating system. Today we are going to allocate page frames.

Managing Available Memory

First we need to know how much memory is available on the computer the OS is running on. The easiest way to do this is to read it from the multiboot structure [19] passed to us by GRUB. GRUB collects the information we need about the memory — what is reserved, I/O mapped, read-only etc. We must also make sure that we don’t mark the part of memory used by the kernel as free (since GRUB doesn’t mark this memory as reserved). One way to know how much memory the kernel uses is to export labels at the beginning and the end of the kernel binary from the linker script:

ENTRY(loader)           /* the name of the entry symbol */

. = 0xC0100000 /* the code should be relocated to 3 GB + 1 MB */

/* these labels get exported to the code files */
kernel_virtual_start = .;
kernel_physical_start = . - 0xC0000000;

/* align at 4 KB and load at 1 MB */
.text ALIGN (0x1000) : AT(ADDR(.text)-0xC0000000)
{
*(.text) /* all text sections from all files */
}

/* align at 4 KB and load at 1 MB + . */
.rodata ALIGN (0x1000) : AT(ADDR(.rodata)-0xC0000000)
{
*(.rodata*) /* all read-only data sections from all files */
}

/* align at 4 KB and load at 1 MB + . */
.data ALIGN (0x1000) : AT(ADDR(.data)-0xC0000000)
{
*(.data) /* all data sections from all files */
}

/* align at 4 KB and load at 1 MB + . */
.bss ALIGN (0x1000) : AT(ADDR(.bss)-0xC0000000)
{
*(COMMON) /* all COMMON sections from all files */
*(.bss) /* all bss sections from all files */
}

kernel_virtual_end = .;
kernel_physical_end = . - 0xC0000000;

These labels can directly be read from assembly code and pushed on the stack to make them available to C code:

extern kernel_virtual_start
extern kernel_virtual_end
extern kernel_physical_start
extern kernel_physical_end

; ...

push kernel_physical_end
push kernel_physical_start
push kernel_virtual_end
push kernel_virtual_start

call kmain

This way we get the labels as arguments to kmain. If you want to use C instead of assembly code, one way to do it is to declare the labels as functions and take the addresses of these functions:

void kernel_virtual_start(void);

/* ... */

unsigned int vaddr = (unsigned int) &kernel_virtual_start;

In the event that you use GRUB modules you need to ensure the memory they use is set apart as held also.

Managing Available Memory

How do we realize which page frames are being used? The page frame allocator needs to monitor which are free and which aren’t. There are a few different ways to do this: bitmaps, connected records, trees, the Buddy System (utilized by Linux) and so forth For more data about the various calculations see the article on OSDev.

Bitmaps are very simple to carry out. The slightest bit is utilized for each page casing and (at least one) page frames are committed to store the bitmap. (Note that this is only one approach to do it, different plans may be better or potentially more amusing to execute.)

How Can We Access a Page Frame?

The page frame allocator returns the actual beginning location of the page frame. This page frame isn’t planned in — no page table focuses to this page frame. How might we peruse and compose information to the casing?

We need to plan the page frame into virtual memory, by refreshing the PDT and additionally PT utilized by the kernel. Consider the possibility that all accessible page tables are full. Then, at that point we can’t plan the page frame into memory, since we’d need another page table — which takes up a whole page frame — and to keep in touch with this page frame we’d need to plan its page frame. Somehow this round reliance should be broken.

One arrangement is to hold a piece of the principal page table utilized by the kernel (or some other higher-half page table) for briefly planning page casings to make them open. In the event that the kernel is planned at 0xC0000000 (page catalog section with record 768), and 4 KB page frames are utilized, then, at that point the kernel has somewhere around one page table. In the event that we accept — or restrict us to — a kernel of size all things considered 4 MB less 4 KB we can commit the last passage (section 1023) of this page table for transitory mappings. The virtual location of pages planned in utilizing the last section of the kernel’s PT will be:

(768 << 22) | (1023 << 12) | 0x000 = 0xC03FF000

After we’ve briefly mapped the page frame we need to use as a page table, and set it up to map in our first page frame, we can add it to the paging directory, and eliminate the transitory mapping.

A Kernel Heap

We’ve simply had the option to work with fixed-size information or crude memory as of recently. We can execute malloc and free to use in the kernel since we have a page frame allocator. The main change we need to do is to conjure the page frame allocator rather than sbrk/brk when additional memory is required. The page frames given by the page frame allocator should likewise be mapped to virtual addresses. When sufficiently large blocks of memory are freed, a good implementation should also return page frames to the page frame allocator on call to free.

Thank you..

--

--