Writing Our Own Memory Manager In C/C++

Requirements
  • GNU/Linux Operating System (Any Distribution)
  • GCC (GNU Compiler Collection with C compiler)
  • Any Editor (Vi/Emacs/Gedit etc.)
This article is divided into two sections, the first is to write our own simple memory allocator (malloc) function and the second is to implement memory management schemes such as Paging & Segmentation.

To allocate memory we will use kernel system call sbrk(). 
 
Introduction

The memory management function keeps track of the status of each memory location, either allocated or free. It determines how memory is allocated among competing processes, deciding which gets memory, when they receive it, and how much they are allowed.

The work of memory manager is to allocate memory, manage memory by providing memory addresses to a Loader, a program that loads another program into main memory using memory addresses provided by memory manager.
 
So how to allocate memory ?

We are using sbrk() system call which takes an positive integer value and increases the size of the heap on main memory.

For more information view main page for it. "main sbrk"
 
Well you have studied data structures at some point in programming, but where and how actually is it used in developing OS?
 
Consider we have many processes for which we need to allocate memory; if we allocate memory for one process and move to second process for allocation,  how will you keep track of last allocated memory block? For this we need to store some metadata with allocated memory block such as pointer points to a next block, memory address, isfree etc.

So it will form a Linked List data structure the same as the following image.
 
So where does our memory block get stored using sbrk() call?

In the following image the break point is the point pointed by "sbrk(0)", if we increases the size by calling sbrk() then the break point increases.
  
Ok now let's start by writing our simple memory allocator program.
  
Remember our program is only the small part of the memory manager implemented by the kernel itself, so whatever the
memory address you see through this; and other programs are Virtual addresses not physical addresses.

This is just an understanding of an implementation of memory manager and their algorithms.
 
You see above linked list image, let's define structure for it to use it as a metadata.
  1. typedef struct __s_block{  
  2.     struct __s_block *next;  
  3.     bool isfree;  
  4.     size_t size;  
  5.     void *memoryAddress;  
  6. }_SBLOCK;  
  7.   
  8.   
  9. #define BLOCK_SIZE sizeof(_SBLOCK)   
next   :   Pointer that points to a next memory block.
isfree :   To check whether memory block is free or not.
size   :   Size which is allocated in the memory.
memoryAddress  :  Starting memory address from where the memory is allocated.
BLOCK_SIZE  :  Size of the structure. 
 
Ok now let's write a function that allocates memory block(allocateMemBlock).
  1. _SBLOCK *allocateMemBlock(size_t size)  
  2. {  
  3.     _SBLOCK *block = (_SBLOCK*)sbrk(0);  
  4.     void *memadr = (void*)sbrk(0);  
  5.     void *allocate_mem = (void*)sbrk(BLOCK_SIZE + size);  
  6.   
  7.     if(allocate_mem == (void*)-1){  
  8.         return NULL;  
  9.     }else{  
  10.         block->next = NULL;  
  11.         block->isfree = false;  
  12.         block->size = size;  
  13.         block->memoryAddress = memadr+BLOCK_SIZE;  
  14.         return block;  
  15.     }  
  16. }   
This function takes an argument of how much size it wants to allocate.  This function is nothing but to add a new node to a linked list.
 
_SBLOCK *block = (_SBLOCK*)sbrk(0);
             First store sbrk(0) means store break point memory address to block so that we can starts our memory allocation from break point.
 
void *memadr = (void*)sbrk(0);
             Store memory address to memadr.
 
void *allocate_mem = (void*)sbrk(BLOCK_SIZE + size);
            Change the location of program break by calling sbrk() call with increment of our metadata block size and how much memory needs to allocate. 
 
Check allocate_mem is equal to (void*)-1 means error then it returns NULL. If not then it stores approriate data into structure variables and returns an allocated block.
 
Ok now we added a new node to our linked list. So we need to add more blocks(nodes) to our linked list.  As you know we are allocating memory for our linked list data structure, so we cannot add a node to the beginning of the list, we need to add it to the end of the list. Here i used singly linked list to manage memory blocks but you can use doubly linked list for both sides.

Here's the function that allocates the next new block and sets the pointers to form a complete linked list.
  1. void allocateNextMemBlock(size_t size, _SBLOCK **head)  
  2. {  
  3.     _SBLOCK *current = *head;  
  4.     void *allocate_mem = NULL;  
  5.     void *memadr = (void*)sbrk(0);  
  6.   
  7.     if(current==NULL){  
  8.         *head = allocateMemBlock(size);  
  9.     }else{  
  10.         while(current->next != NULL){  
  11.             current = current->next;  
  12.         }  
  13.         _SBLOCK *newblock = sbrk(0);  
  14.   
  15.         allocate_mem = (void*)sbrk(BLOCK_SIZE + size);        
  16.         if(allocate_mem == (void*) - 1){ }  
  17.         else{  
  18.             newblock->next = NULL;  
  19.             newblock->isfree = false;  
  20.             newblock->size = size;  
  21.             newblock->memoryAddress = memadr+BLOCK_SIZE;  
  22.             current->next = newblock;  
  23.       }  
  24.     }  
  25. }   
First get the last memory block by traversing, add a new node to the next node of the current last node.
 
You can see that we are using isfree variable to check whether current memory block is free or not. Freeing memory means to just overwrite the contents. So for freeing the memory we will set isfree variable value to true. When another block wants to allocate some memory it will check only this variable value for true.
  1. void freeMemBlock(_SBLOCK **head)  
  2. {  
  3.     if(*head == NULL){}  
  4.     else{  
  5.         (*head)->isfree = true;  
  6.     }  
  7. }   
Here's the complete program for simple memory allocator.
  1. #include<stdio.h>  
  2. #include<stdbool.h>  
  3.   
  4. typedef struct __s_block{  
  5.     struct __s_block *next;  
  6.     bool isfree;  
  7.     size_t size;  
  8.     void *memoryAddress;  
  9. }_SBLOCK;  
  10.   
  11. #define BLOCK_SIZE sizeof(_SBLOCK)  
  12.   
  13. _SBLOCK *allocateMemBlock(size_t size)  
  14. {  
  15.     _SBLOCK *block = (_SBLOCK*)sbrk(0);  
  16.     void *memadr = (void*)sbrk(0);  
  17.     void *allocate_mem = (void*)sbrk(BLOCK_SIZE + size);  
  18.   
  19.     if(allocate_mem == (void*)-1){  
  20.         return NULL;  
  21.     }else{  
  22.         block->next = NULL;  
  23.         block->isfree = false;  
  24.         block->size = size;  
  25.         block->memoryAddress = memadr+BLOCK_SIZE;  
  26.         return block;  
  27.     }  
  28. }  
  29.   
  30.   
  31. //allocate next memory block  
  32. void allocateNextMemBlock(size_t size, _SBLOCK **head)  
  33. {  
  34.     _SBLOCK *current = *head;  
  35.     void *allocate_mem = NULL;  
  36.     void *memadr = (void*)sbrk(0);  
  37.   
  38.     if(current==NULL){  
  39.         *head = allocateMemBlock(size);  
  40.     }else{  
  41.         while(current->next != NULL){  
  42.             current = current->next;  
  43.         }  
  44.         _SBLOCK *newblock = sbrk(0);  
  45.   
  46.         allocate_mem = (void*)sbrk(BLOCK_SIZE + size);        
  47.         if(allocate_mem == (void*) - 1){ }  
  48.         else{  
  49.             newblock->next = NULL;  
  50.             newblock->isfree = false;  
  51.             newblock->size = size;  
  52.             newblock->memoryAddress = memadr+BLOCK_SIZE;  
  53.             current->next = newblock;  
  54.       }  
  55.     }  
  56. }  
  57.   
  58.   
  59. void freeMemBlock(_SBLOCK **head)  
  60. {  
  61.     if(*head == NULL){}  
  62.     else{  
  63.         (*head)->isfree = true;  
  64.     }  
  65. }  
  66.   
  67.   
  68. void printMemBlocks(_SBLOCK *current)  
  69. {  
  70.     while(current != NULL){  
  71.         printf("isfree = %d, size = %d, memoryAddress = %p, current = %p, next-node = %p\n",  
  72.                 current->isfree, current->size, current->memoryAddress, current, current->next);  
  73.         current = current->next;  
  74.     }  
  75. }  
  76.   
  77.   
  78. int main()  
  79. {  
  80.     _SBLOCK *sMemBlock = NULL;  
  81.     allocateNextMemBlock(10,&sMemBlock);  
  82.     allocateNextMemBlock(35,&sMemBlock);  
  83.     allocateNextMemBlock(62,&sMemBlock);  
  84.     printMemBlocks(sMemBlock);  
  85.       
  86.     printf("\nAfter freeing second node\n");  
  87.     freeMemBlock(&(sMemBlock->next));  
  88.     printMemBlocks(sMemBlock);  
  89.   
  90.     return 0;  
  91. }   
Compile this program using gcc -w <file_name>.c command and then execute it(./a.out). 
 
For the rest of the programs I am reading their input from a file which contains a number of processes and how much memory they need. To read data from a file I am using linked lists by reading each character from a file, and forming real data from it. I  know we can directly read integers from a file, but if we want to read some specific data labeled by a string then you cannot do that by just reading an integer, you may lose that specific data.

I have written a simple parser for reading data from a file(getDataFromFile.c).
 
Allocate All Memory
 
If a given process's size is enough to satisfy the memory then allocate all the memory to all processes. This is good for embeded systems which have finite memory and processes but not in general. In allocating all memory we just need to add nodes in the linked list and print their memory addresses. where : _PROCINTNODE is the linked list that contains process id & required size. It is a structure defined like this.
  1. typedef struct __procintnode {  
  2.     int process;  
  3.     int size;  
  4.     struct __procintnode *next;  
  5. }_PROCINTNODE;  
  1. void allocate_allMemory(_SBLOCK **blockHead,_PROCINTNODE *procinthead)  
  2. {  
  3.     _PROCINTNODE *current = procinthead;  
  4.   
  5.     printf("\n\t\t[ All Memory allocated to processes]\n");  
  6.     printf("-------------------------------------------------------------------------");  
  7.     printf("\n|  Process   |   Start Address   |   End Address    |      Size       |\n");  
  8.     printf("-------------------------------------------------------------------------\n");  
  9.   
  10.     while(current != NULL){  
  11.         void *start_address = sbrk(0) + 1;  
  12.         allocateNextMemBlock(current->size + 1, &(*blockHead));  
  13.         void *end_address = sbrk(0);  
  14.         float size_kb = (current->size)/1024.0;  
  15.   
  16.         printf("|     P%d     |     %p     |    %p     |     %.3fKB     |\n",  
  17.                 current->process, start_address, end_address, size_kb);  
  18.   
  19.         current = current->next;  
  20.     }  
  21.     printf("-------------------------------------------------------------------------\n");  
  22.     printf("\n\n");  
  23.     printf("\n\tCurrent brk pointer is here (sbrk(0)) = %p\n", sbrk(0));  
  24.       
  25.   
  26.     //release the allocated memory  
  27.     printf("\n\t\t\t[ Memory released ]\n\n");  
  28.     _SBLOCK *lastblock, *blockcurrent = *blockHead;  
  29.     while(blockcurrent != NULL){  
  30.         blockcurrent->isfree = true;  
  31.         if(blockcurrent->next == NULL){  
  32.                 lastblock = blockcurrent;  
  33.         }  
  34.         blockcurrent = blockcurrent->next;  
  35.     }  
  36.   
  37.     blockcurrent = lastblock;  
  38.     int proc_length = _countPROCINTNodes(procinthead);  
  39.   
  40.     while(blockcurrent->prev != NULL){  
  41.         if(blockcurrent->isfree){  
  42.             (blockcurrent->prev)->next = NULL;  
  43.             if(blockcurrent->prev == NULL){  
  44.                 sbrk(0-(BLOCK_SIZE + blockcurrent->size + 1));  
  45.                 printf("\nProcess P%d : sbrk(0) is %p\n", proc_length, sbrk(0));          
  46.             }  
  47.             sbrk(0-(BLOCK_SIZE + blockcurrent->size + 1));  
  48.             printf("\nProcess P%d : sbrk(0) is %p\n", proc_length, sbrk(0));  
  49.         }  
  50.         blockcurrent = blockcurrent->prev;  
  51.         proc_length--;  
  52.     }  
  53.   
  54.     sbrk(0 - (BLOCK_SIZE + blockcurrent->size + 1));  
  55.     printf("\nProcess P%d : sbrk(0) is %p\n",proc_length,sbrk(0));  
  56.     *blockHead=NULL;   
  57.   
  58. }   
Paging
 
Paging is the memory management scheme in which each process is divided into equal size of pages. Then the memory is allocated for each page for its processing.

Paging gives the idea of Virtual memory. Virtual memory is the memory whose smallest part is of the physical memory address. Virtual memory addresses could be infinite. Then the mapping is done to calculate the actual physical memory address. 
  
But the size of all the memory is fixed, we need to reuse those page blocks which have done their jobs. For this you can use many algorithms such as Most Recently Used(MRU), Least Recently Used(LRU), Most Frequently Used(MFU) etc.
  
As far as our memory management program, we dont know what is the physical address, so we cannot do mapping. We will just use the virtual addresses provided by the kernel for our program.
 
For our implementation of paging, we need two tables, Virtual & Physical table.
 
Download the source code to view code for implementation of paging.

See the following image.
  
  1. #define MAX_MEM_SIZE (1024*1024)  
  2.   
  3. #define PAGE_SIZE 4096  
  4. #define MAX_PAGES 256  
  5. #define PAGE_MEM_SIZE 0x256  
  6.   
  7.   
  8. //structure for store virtual memory data using paging  
  9. typedef struct __memvirtpageblocks{  
  10.     struct __memvirtpageblocks *next;  
  11.     size_t size;  
  12.     int process;  
  13.     int pagenumber;  
  14.     void *memoryaddress;  
  15. }_VIRTMEMPAGEBLOCKS;  
  16.   
  17.   
  18. //structure for allocate memory using paging  
  19. typedef struct __mempageblocks{  
  20.     struct __mempageblocks *next;  
  21.     bool isfree;  
  22.     void *memoryaddress;  
  23. }_MEMPAGEBLOCKS;  
  24.   
  25. #define MEM_PAGE_BLOCK_SIZE sizeof(_MEMPAGEBLOCKS)   
MAX_MEM_SIZE   :  Which is our maximum whole memory(main memory) size which is 1MB.
MAX_PAGES   :   Maximum number of pages.
PAGE_MEM_SIZE   :  Size of each page.

I used three functions to implement paging,
  1. void divideProc_Mem_IntoPageBlocks(_PROCINTNODE *procinthead,  
  2.                                                              _VIRTMEMPAGEBLOCKS **virtmempageblockshead,  
  3.                                                              _MEMPAGEBLOCKS **mempageblocksHead)  
  4.   
  5. void mapVirtPhyAddressPageTable(_VIRTMEMPAGEBLOCKS **virtmempageblockshead,  
  6.                                                          _MEMPAGEBLOCKS **mempageblocksHead)  
  7.   
  8. void AllocatePAGING(_SBLOCK *s_blockHead,  
  9.                                    _VIRTMEMPAGEBLOCKS *virtmempageBlocks,  
  10.                                    _MEMPAGEBLOCKS *mempageBlocks,  
  11.                                    const char *inputFile)   
In the first function I am dividing our process & memory into equal sized  page blocks and forming a linked list of them(_VIRTMEMPAGEBLOCKS & _MEMPAGEBLOCKS).

In the second function I am mapping a virtual to physical address, as you know we dont know the actual physical address, so we will just print the address provided by sbrk() sys call.

In the third function I am calling all paging functions, and reading data from inputFile etc.
 
Segmentation
 
This is another memory management scheme which is like paging but it uses segments whose size is variable rather than fixed. Segmentation also provides a virtual memory. Also in this scheme memory mapping is done to find the actual physical memory address.
 
As I said the size of each segment is variable, but how much ?

You know memory is fixed in size, so you cannot allocate whole memory to only one process. So the size of the segment is also fixed but this fixed size is used only when it's needed. If some process requires a lesser size than the fixed segment then allocate memory only for that process otherwise allocate fixed segment size. Segmentation consists of ansegment table which contains segment number, size, process id, physical address, virtual address etc.
 
Download the source code to view code for implementation of segmentation.

See following image.
    
  1. #define MAX_SEGMENT_SIZE 256000  
  2.   
  3. typedef struct __memsegmentblocks{  
  4.     struct __memsegmentblocks *next;  
  5.     bool isfree;  
  6.     size_t size;  
  7.     void *memoryaddress;  
  8. }_MEMSEGMENTBLOCKS;  
  9.   
  10.   
  11. #define MEM_SEGMENT_BLOCK_SIZE sizeof(_MEMSEGMENTBLOCKS)  
I used four functions to implement segmentation
  1. void allocateMemory_using_Segmentation(_PROCINTNODE **procinthead,  
  2.                                                                      _MEMSEGMENTBLOCKS **memSegBlockshead)  
  3.   
  4. void getFreeMemoryAddress(_MEMSEGMENTBLOCKS *memSegBlockshead,  
  5.                                                 unsigned int size,  
  6.                                                 void **start_address)  
  7.   
  8. void allocateMemory_using_Segment_remain(_PROCINTNODE **procinthead,  
  9.                                                                            _MEMSEGMENTBLOCKS **memSegBlockshead)  
  10.   
  11. void AllocateSEGMENTATION(_SBLOCK *s_blockHead,  
  12.                                                _MEMSEGMENTBLOCKS *memSegBlocksHead,  
  13.                                                const char *inputFile)   
In the first function i am allocating memory to each process according to their size. Once memory is allocated to some processes memory gets full, so we will just free the memory. Using second & third functions i am allocating memory to those processes whose task is still not completed. In the fourth function I am calling all segmentation functions by reading data from inputFile. 
 
Alright you might be wondering what this program does; well, it allocates a memory in the form of a linked list, and generates the memory addresses. These memory addresses are then used by the program loader & processes to complete its task in the allocated memory addresses.

This is just an abstract implementation of memory manager which is used in operating systems using standard C libraries. 

Up Next
    Ebook Download
    View all
    Learn
    View all