Writing your own good memory manager

    Good time of day, reader. Perhaps you have already read my previous articles, and you know that I am writing my own OS. Today we will talk, and we will consider a simple and fast enough memory management algorithm - a memory manager is a critical part of the OS, because fast, reliable and non-memory memory is a pledge of a good OS.
    I was looking for simple and adequate ideas for the manager in RuNet, and on English-language sites - I still could not find any good articles about adequate, not working for O (N) allocator. Well, today we are going to consider a better idea for the memory manager, I’m going to continue the text under cat.

    Theory


    From a wiki: A memory manager is a part of a computer program (both an application and an operating system) that processes requests for allocating and freeing RAM or (for some computer architectures) requests for the inclusion of a given memory area in the processor's address space.

    I suggest also to read this article before continuing .

    Watermak allocator


    Well, probably the easiest of all the allocators is the Watermark Allocator. Its essence is approximately the following: all memory is divided into blocks, the block has a header containing the size of this and the previous block, the status of the block (busy / free), knowing the address of the block we can get the address of the next and previous block by O (1).



    Memory allocation

    To allocate memory, we simply need to run in blocks, and look for a block whose size is greater than or equal to the size required for allocating memory. As you already understood, the asymptotic behavior of O (N) is bad.

    Memory release

    To free up memory, we just need to set the block status as “free” - O (1) - super!

    But, as you understand, holes can start to form, in which 2 or more free blocks - memory is defragmented, when released, you can view the next blocks, and in the case when one or two are free - merge them into one.

    Allocator for logarithmic speed


    We know that we need to search only among the free blocks. Running only for free improves speed on average twice, but it's still a line. Well, why should we run through all the blocks, looking for size, if we can organize a tree of free blocks! Initially, we have only one free block, and then we add free blocks to the binary search tree, the key will be the block size. Thus, to allocate memory, it is enough for us to find a block in a tree whose size is greater than or equal to what we need. We calmly do this for O (log N), just going down the tree. Then we either cut the block in two, or completely give it to the one who requested the memory. Then we remove the block from the tree in O (1). And, if necessary, insert the rest of the block back in O (log N). For release, we just need to insert the block back,

    It is logical that you do not need to use a simple tree, you must use a self-balancing tree, Red-Black or AVL. You can store a tree of blocks in a static array, or think of how to do it differently.

    Actually, the code:

    char * malloc(size_t size){
    	if (!size) {
    		return0;
    	}
    	char * addr = 0;
    	int i;
    	for (i = 0; i < allocationAvlTree.size; )
    	{
    		int r;
    		node_t *n;
    		n = &allocationAvlTree.nodes[i];
    		/* couldn't find it */if (!n->key) {
    			returnNULL;
    		}
    		r = allocationAvlTree.cmp(n->key, size);
    		if (r <= 0)
    		{
    			//We're lucky today.//Get memory block headeralloc_t * block = (size_t)n->val - sizeof(alloc_t);
    			//Set to used
    			block->status = 1;
    			//Set size
    			block->size = size;
    			alloc_t * next = (size_t)n->val + size;
    			next->prev_size = size;
    			next->status = 0;
    			next->size = n->key - size - 16;
    			avltree_remove(&allocationAvlTree, n->key, n->val);
    			if (n->key - size - 16)
    				avltree_insert(&allocationAvlTree, next->size, (size_t)next + sizeof(alloc_t));
    			memset((size_t)block + sizeof(alloc_t), 0, block->size); 
    			block->signature = 0xDEADBEEF;
    			unlockTaskSwitch();
    			return (size_t)block + sizeof(alloc_t);
    		}
    		elseif (r > 0)
    			i = __child_r(i);
    		else
    			assert(0);
    	}
    	return0;
    }
    voidfree(void * mem){
    	if (!mem)
    		return;
    	//Get current allocalloc_t * alloc = ((unsignedint)mem - sizeof(alloc_t));
    	if (alloc->signature != 0xDEADBEEF)
    		return;
    	alloc->status = 0;
    	alloc_t * left = ((unsignedint)alloc - sizeof(alloc_t) - alloc->prev_size);
    	if (left->signature == 0xDEADBEEF&&left->status == 0&&left->size==alloc->prev_size)
    	{
    		//Merge blocksif (avltree_remove(&allocationAvlTree, left->size, (uint)left + sizeof(alloc_t))) {
    			left->size += sizeof(alloc_t) + alloc->size;
    			alloc = left;
    		}
    		else assert(0);
    	}
    	alloc_t * right = (uint)alloc + sizeof(alloc_t) + alloc->size;
    	if (right->prev_size&&right->status == 0&&right->signature == 0xDEADBEEF)
    	{
    		if (avltree_remove(&allocationAvlTree, right->size, (uint)right + sizeof(alloc_t)))
    			alloc->size += sizeof(alloc_t) + right->size;
    		else assert(0);
    	}
    	avltree_insert(&allocationAvlTree, alloc->size, (uint)alloc + sizeof(alloc_t));
    }
    

    Good luck and ethical hacking! Any objective criticism is welcome, the purpose of the article is not to say that it is a wah which allocator, but simply to tell about the allocator, which will be better than the stupid implementations of simple allocators.

    Also popular now: