Osdev-Notes

Memory Management

Welcome to the first challenge of our osdev adventure! Memory management in a kernel is a big area, and it can easily get very complex. This chapter aims to breakdown the various layers you might use in your kernel, and explain how each of them is useful.

The design and complexity of a memory manger can vary greatly, a lot depends on what the operating system is designed, and its specific goals. For example if only want mono-tasking os, with paging disabled and no memory protection, it will probably be fairly simple to implement.

In this part we will try to cover a more common use case that is probably what nearly all modern operating system uses, that is a 32/64 operating system with paging enabled, and various forms of memory allocators for the kernel and one for user space.

In the appendices there is also an additional section on memory protection features available in some CPUs.

We will cover the following topics:

Authors note: don’t worry, we will try to keep it as simple as possible, using basic algorithms and explaining all the gray areas as we go. The logic may sometimes be hard to follow, you will most likely have to go through several reads of this part multiple times.

Each of the layers has a dedicated chapter, however we’ll start with a high level look at how they fit together. Before proceeding let’s briefly define the concepts above:

Memory Management Layer Description
Physical Memory Manager Responsible for keeping track of which parts of the available hardware memory (usually ram) are free/in-use. It usually allocates in fixed size blocks, the native page size. This is 4096 bytes on x86.
Paging It introduces the concepts of virtual memory and virtual addresses, providing the OS with a bigger address space, protection to the data and code in its pages, and isolation between programs.
Virtual memory manager For a lot of projects, the VMM and paging will be the same thing. However the VMM should be seen as the virtual memory manager, and paging is just one tool that it uses to accomplish its job: ensuring that a program has memory where it needs it, when it needs it. Often this is just mapping physical ram to the requested virtual address (via paging or segmentation)
Heap Allocator The VMM can handle page-sized allocations just fine, but that is not always useful. A heap allocator allows for allocations of any size, big or small.

A Word of Wisdom

As said at the beginning of this chapter Memory management is one of the most important parts of a kernel, as every other part of the kernel will interact with it in some way. It’s worth taking the extra time to consider what features we want our PMM and VMM to have, and the ramifications. A little planning now can save us a lot of headaches and rewriting code later!

PMM - Physical Memory Manager

The main features of a PMM are:

Usually this is the lowest level of allocation, and only the kernel should access/use it.

Paging

Although Paging and VMM are strongly tied, let’s split this topic into two parts: with paging we refer to the hardware paging mechanism, that usually involeves tables, and registers and address translation, while the VMM it refers to the higher level (usually architecture independant).

While writing the support for paging, independently there are few future choices we need to think about now:

VMM - Virtual Memory Manager

The VMM works tight with paging, but it’s a layer above, usually its main features are:

Similarly to paging there are some things we need to consider depending on our future decisions:

Heap Allocator

There is a disntiction to be made here, between the kernel heap and the program heap. Many characteristic are similar between each other, although different algorithm can be used. Usually there is just one kernel heap, while every program will have its own userspace heap.

The heap is implemented above the VMM, for the kernel one:

An Example Workflow

To get a better picture of how things work, let’s describe from a high level how the various components work together with an example. Suppose we want to allocate 5 bytes:

char *a = alloc(5);

What happens under the hood?

  1. The alloc request the heap for pointer to an area of 5 bytes.
  2. The heap allocator searches for a region big enough for 5 bytes, if available in the current heap. If so, no need to dig down further, just return what was found. However if the current heap doesn’t contain an area of 5 bytes that can be returned, it will need to expand. So it asks for more space from the VMM. Remember: the addresses returned by the heap are all virtual.
  3. The VMM will allocate a region of virtual memory big enough for the new heap expansion. It then asks the physical memory manager for a new physical page to map there.
  4. Lastly a new physical page from the PMM will be mapped to the VMM (using paging for example). Now the VMM will provide the heap with the extra space it needed, and the heap can return an address using this new space.

The picture below identifies the various components of a basic memory management setup and show how they interact in this example scenario.

Memory management Workflow Example