Navigation
Highlights

Release 0.0.5

The latest release, alpha testing, unstable. See "downloads".

2005-06-01

Links
Documentation

Physical Memory Manager

The Physical Memory Manager (PMM from now) is the part of the FreeDOS-32 kernel that manages system physical memory in order to allocate and free memory areas dynamically.

Initialization

The PMM manages sets of memory areas, i.e. contiguous ranges of memory addresses, called memory pools. At the moment two distinct memory pools are defined: one for memory below 1 MiB and another for memory above 1 MiB. Currently only the second memory pool is used, while the memory below 1 MiB, currently managed by OSLib functions, will become the DOS memory pool. The second memory pool will be probably splitted in two distinct ones: one for DMA memory (below 16 MiB) and one for all other memory (above 16 MiB).

Available memory

The MultiBoot boot loader (either GRUB or X.exe) detects the amount of memory present on the machine, and passes this information to the kernel, thus the PMM doesn't have to do this task itself. The MultiBoot Information (MBI) structure contains two fields, representing the amount of free memory in the first MB and above the first MB. Moreover, it can provide a memory map, that should be used if the system has memory "holes", such as ROM of some physical device, or the VBE LFB. From this available memory, we must remove the memory used by the kernel itself and by MultiBoot modules.

Setting the PMM up

The memory manager is initialized with 0 available memory. If a memory map is not provided, we can add a free memory area with the size provided by the MBI, whereas if a memory map is provided, we can add all the free memory regions from the map. Next we remove the memory region used by the kernel (using two symbols telling us the first and the last memory location used by the kernel) and memory regions used by the MultiBoot modules loaded (the MBI provides the starting address and the size for each module).

Interface

The following functions are provided by the PMM to manipulate memory pools:

void pmm_init(struct mempool *mp);

Initializes the memory pool descriptor with zero available memory.

void pmm_free(struct mempool *mp, DWORD base_addr, DWORD size);

Deallocates a memory area from base_addr to base_addr+size, adding a memory area to the specified pool. This function is also used by the memory initialization code to add the system memory to the free memory pool.

DWORD pmm_alloc(struct mempool *mp, DWORD size);

Allocates a memory area of the specified size, returning the start address on success or 0 on failure.

int pmm_alloc_address(struct mempool *mp, DWORD base_addr, DWORD size);

Allocates a memory area from base_addr to base_addr+size, removing a memory area from the specified pool. Returns a number < 0 on failure, >= 0 on success. This function is also used by the memory initialization code to remove the memory regions used by the kernel and by the modules from the free memory pool.

Important note: the PMM functions should never be called directly by anyone. The following wrappers shall be used instead:

int mem_free(DWORD base, DWORD size);
int mem_get_region(DWORD base, DWORD size);
DWORD mem_get(DWORD amount);

These are the only functions that can interact with the PMM. This is done in order to hide the concept of memory pool inside the kernel. If the memory manager structure is changed, only these wrappers will need modifications. All the kernel components that use the PMM through these wrappers will not have to be modified in order to cope with this changes.

Internals

Each memory pool consists of a list of free memory areas. When a memory area is allocated it is removed from the list, while when a memory area is freed it is added to the list.

PMM data structures

A free memory area is described by the following 8-bytes structure:

struct memheader {
  struct memheader *next;
  DWORD size;
};

where next is a pointer to the header of the next memory area in the list and size is the size of the memory area. The header overwrites the first 8 bytes of the memory area. Hence, the area described by a struct memheader *h goes from address h to address h + h->size.

A memory pool is described by the following structure:

struct mempool {
  DWORD free;
  struct memheader *first;
};

where free is amount of free memory in the pool (not really needed, but it can be probably useful) and first is the pointer to the head of a list of memory area headers.

How pmm_init works

pmm_init initializes the memory pool descriptor, setting free to 0 and the first pointer to NULL.

How pmm_free works

pmm_free adds a memory area (specified by parameters) to the memory pool. The memory area is described by its base address and its size. In theory, this function should simply insert a new element in the memory pool list:

  • the area header overlaps the first 8 bytes of the area: h = (struct memheader *) base;
  • fills the size field in the header: h->size = size;
  • inserts the header in the list: h->next = mp->first; mp->first = h;

In practice, the code is a little more complex, because the memory pool list is ordered by increasing base address, and pmm_free() also checks if the added memory can be added to some memory area that is already in the list. Also a sanity check is performed to avoid freeing memory that is already free.

How pmm_alloc works

pmm_alloc searches the memory pool list for the first area with a size that is bigger than the requested amount of memory. When such a memory area is found, its size is decreased by the requested amount of memory, and the required memory is allocated from it. Note that the memory is allocated at the end of the area, in order to simplify the code (in the most common case the list is not modified, and only the size field of the memory header has to be changed).

How pmm_alloc_address works

pmm_alloc_address is similar to pmm_alloc, but it allocates the memory at a specified address. For this reason pmm_alloc_address is more complex than pmm_alloc; in fact it has to browse the memory pool list searching for an area that contains the requested memory: if ((b <= base) && ((b + p->size) >= (base + size))) {

Moreover, the memory cannot be allocated at the end of the area, hence the list has to be modified in a more complex way (in general, the allocated memory divides a memory area in two parts, hence a new memory area has to be created...).

Considerations

Looking at the previous description, you can note three things:

  • pmm_free is different from the libc free() function, because pmm_free takes the amount of memory to free as an input. This is not a problem for a memory allocator used in the kernel.
  • If a memory area smaller than 8 bytes (the size of struct memheader) has to be put in the memory pool list, we can have memory corruption. This hopefully is a very uncommon situation, hence I avoided the problem by simply leaving the area unallocated (look at the WARNING messages in the code...). In practice, we lose up to 8 bytes of memory each time that this situation occurs.
  • The pmm_alloc_address efficiency depends on how the memory pool list is ordered. This list is currently ordered by base address, in order to simplify the implementation of pmm_free (with a different order, it would be more complex to collapse continous memory areas), but some day we will have to test if some other ordering strategy can improve the memory allocation performance. In order to do that, only the pmm_free function has to be modified. Currently, I am thinking about two possible strategies: smallest area first or biggest area first.