Написание собственного неплохого менеджера памяти

в 22:09, , рубрики: allocators, C, kernel, memory managment, Алгоритмы, Занимательные задачки, Программирование

Доброе время суток, читатель. Возможно, вы уже читали мои предыдущие статьи, и знаете, что я занимаюсь написанием собственной ОС. Сегодня мы поговорим, и рассмотрим несложный и достаточно быстрый алгоритм для управления памятью — менеджер памяти — критически важная часть ОС, ведь быстрая, надежная и нерастратная работа с памятью залог хорошей ОС.
Искал я несложные и адекватные идеи для менеджера и в рунете, и на англоязычных сайтах — всё никак не мог найти никаких хороших статей про адекватный, работающий не за O(N) аллокатор. Что же, сегодня мы рассмотрим более хорошую идею для менеджера памяти, продолжение помещаю под кат.

Теория

Из вики: Менеджер памяти — часть компьютерной программы (как прикладной, так и операционной системы), обрабатывающая запросы на выделение и освобождение оперативной памяти или (для некоторых архитектур ЭВМ) запросы на включение заданной области памяти в адресное пространство процессора.

Предлагаю так же перед продолжением прочитать эту статью.

Watermak Allocator

Что же, наверное самый простой из всех аллокаторов — Watermark Allocator. Суть его примерно следующая: вся память разбита на блоки, у блока есть заголовок, в котором лежит размер этого и предыдущего блока, статус блока(занят/свободен), зная адрес блока мы за O(1) можем получить адрес следующего и предыдущего блока.

Написание собственного неплохого менеджера памяти - 1

Выделение памяти

Для выделения памяти нам просто напросто нужно бежать по блокам, и искать блок, размер которого больше или равен размеру требуемой для выделения памяти. Как вы уже поняли, асимптотика O(N) — плохо.

Высвобождение памяти

Для высвобождения памяти нам достаточно установить статус блока как «свободный» — O(1) — супер!

Но, как вы понимаете, могут начать образовываться дыры, в которых 2 и более свободных блоков — память дефрагментированна, при высвобождении можно просматривать соседние блоки, и в случае, когда один или два свободны — мержить их в один.

Аллокатор за логарифмическую скорость

Мы знаем, что нам надо искать только среди свободных блоков. Бегать только по свободным улучшает скорость в среднем в два раза, но это всё равно линия. Что же, зачем нам бегать по всем блокам, ища размер, если мы можем организовать дерево из свободных блоков! Изначально у нас только один свободный блок, а дальше мы добавляем свободные блоки в бинарное дерево поиска, ключом будет — размер блока. Таким образом, чтобы выделить память нам достаточно найти блок в дереве, у которого размер больше или равен тому, что нам нужен. Это мы спокойно делаем за O(log N), просто спускаясь по дереву вниз. Дальше мы либо режем блок на два, либо полностью отдаем его тому, кто запросил память. Дальше мы удаляем блок из дерева за O(1). И, если надо, вставляем оставшуюся часть блока обратно за O(log N). Для высвобождения нам достаточно вставить блок обратно, и не надо забывать про фрагментацию.

Логично, что использовать простое дерево не надо, надо использовать самобалансирующееся дерево, Red-Black или AVL. Хранить дерево блоков можно в статическом массиве, или же придумать как это сделать по-другому.

Собственно, код:

char * malloc(size_t size) {
	if (!size) {
		return 0;
	}
	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) {
			return NULL;
		}
		r = allocationAvlTree.cmp(n->key, size);
		if (r <= 0)
		{
			//We're lucky today.
			//Get memory block header
			alloc_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);
		}
		else if (r > 0)
			i = __child_r(i);
		else
			assert(0);
	}
	return 0;
}

void free(void * mem) {
	if (!mem)
		return;
	//Get current alloc
	alloc_t * alloc = ((unsigned int)mem - sizeof(alloc_t));
	if (alloc->signature != 0xDEADBEEF)
		return;
	alloc->status = 0;
	alloc_t * left = ((unsigned int)alloc - sizeof(alloc_t) - alloc->prev_size);
	if (left->signature == 0xDEADBEEF&&left->status == 0&&left->size==alloc->prev_size)
	{
		//Merge blocks
		if (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));

}

Удачи и этичного хакинга! Любая объективная критика приветствуется, цель статьи не сказать, что это вах какой аллокатор, а просто рассказать о аллокаторе, который будет лучше, чем тупые реализации простых аллокаторов.

Автор: mrlolthe1st

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js