400行程序写一个实时操作系统(七):内存管理算法的完整实现

143 阅读4分钟

上一篇笔者讲了内存分配的算法,是适合让我们一步步实现这些算法了,一共有四个函数,分别是初始化、分配、释放、插入。

初始化

初始化后的内存分布如图所示:

初始化

初始化后的内存分布如图所示:

简单来说,就是我们要在大内存数组起始地址和终结地址分别创建两个heap_node节点,用于管理这个内存块,然后将它们使用指针连起来,还记得上一篇博客中我们定义的theheap吗?它负责记录头部和尾部。

像&= ~这些操作是字节对齐,字节对齐可以提高CPU运行速度,也可以防止error的发生

另外还要给读者介绍一点小知识:对于正整数X,Y = 2^n,X%Y 可以被X&(Y-1)代替。

举个例子,X % 16 = X &15。

证明过程看笔者博客:c程序杂谈系列(加减乘除模篇)-CSDN博客

程序如下:此时,firstnode就是头部。

​
void heap_init()
{
    heap_node *firstnode;
    uint32_t start_heap ,end_heap;
    //get start address
    start_heap =(uint32_t) allheap;
    if( (start_heap & aligment_byte) != 0){
        start_heap += aligment_byte ;
        start_heap &= ~aligment_byte;
        theheap.allsize -=  (size_t)(start_heap - (uint32_t)allheap);//byte aligment means move to high address,so sub it!
    }
    theheap.head.next = (heap_node *)start_heap;
    theheap.head.blocksize = (size_t)0;
    end_heap  = ( start_heap + (uint32_t)config_heap);
    if( (end_heap & aligment_byte) != 0){
        end_heap += aligment_byte ;
        end_heap &= ~aligment_byte;
        theheap.allsize =  (size_t)(end_heap - start_heap );
    }
    theheap.tail = (heap_node*)end_heap;
    theheap.tail->blocksize  = 0;
    theheap.tail->next =NULL;
    firstnode = (heap_node*)start_heap;
    firstnode->next = theheap.tail;
    firstnode->blocksize = theheap.allsize;
}

分配

分配的算法如下,程序使用的是first-fit算法:

在初始化后,空闲内存块已经形成了一条链表,这条链表被称为空闲内存块链表,内存块被使用就被踢出这条链表,被释放就加入这条链表,如果内存块大于要申请的大小,就把多余的部分切割,然后加入这条内存链表。

源码,malloc会找到合适的内存块,并且返回它的地址:

​
void *heap_malloc(size_t wantsize)
{
    heap_node *prevnode;
    heap_node *usenode;
    heap_node *newnode;
    size_t aligmentrequisize;
    void *xReturn = NULL;
    wantsize += heapstructSize;
    if((wantsize & aligment_byte) != 0x00)
    {
        aligmentrequisize = (aligment_byte + 1) - (wantsize & aligment_byte);//must 8-byte alignment
        wantsize += aligmentrequisize;
    }
    if(theheap.tail== NULL )
    {
        heap_init();
    }
    prevnode = &theheap.head;
    usenode = theheap.head.next;
    while((usenode->blocksize) < wantsize )
    {
        prevnode = usenode;
        usenode = usenode ->next;
    }
    xReturn = (void*)( ( (uint8_t*)usenode ) + heapstructSize );
    prevnode->next = usenode->next ;
    if( (usenode->blocksize - wantsize) > MIN_size )
    {
        newnode = (void*)( ( (uint8_t*)usenode ) + wantsize );
        newnode->blocksize = usenode->blocksize - wantsize;
        usenode->blocksize = wantsize;
        newnode->next = prevnode->next ;
        prevnode->next = newnode;
    }
    theheap.allsize-= usenode->blocksize;
    usenode->next = NULL;
    return xReturn;
}

释放

当有内存块需要释放时,直接插入空闲内存块链表即可即可。

插入前:

插入后:

程序如下:

void heap_free(void *xret)
{
    heap_node *xlink;
    uint8_t *xFree = (uint8_t*)xret;
​
    xFree -= heapstructSize;
    xlink = (void*)xFree;
    theheap.allsize += xlink->blocksize;
    InsertFreeBlock((heap_node*)xlink);
}
​

InsertFreeBlock不仅实现插入,同时实现合并内存块

插入与合并

如果我们检测到free释放的内存块的地址与其他空闲内存块是连续的,我们会合并它们,这里是内存管理算法的精髓所在。

后相邻插入合并:

等价于:

前相邻插入合并:

等价于:

前+后:注:B直接指向E

等价于:

如果前后相邻是相互独立的,那么互不影响,如果不独立,通过后,前的顺序可以得到前后都相邻的结果,因此该顺序正确。

可能有读者看不懂这些图是什么意思,请你对照着代码画一下图片,描述各个节点的关系,然后你再来看一下我的图片,相信你一定会豁然开朗。

程序如下:

​
static void InsertFreeBlock(heap_node* xInsertBlock)
{
    heap_node *first_fitnode;
    uint8_t* getaddr;
​
    for(first_fitnode = &theheap.head;first_fitnode->next < xInsertBlock;first_fitnode = first_fitnode->next)
    { /*finding the fit node*/ }
​
    xInsertBlock->next = first_fitnode->next;
    first_fitnode->next = xInsertBlock;getaddr = (uint8_t*)xInsertBlock;
    if((getaddr + xInsertBlock->blocksize) == (uint8_t*)(xInsertBlock->next))
    {
        if(xInsertBlock->next != theheap.tail )
        {
            xInsertBlock->blocksize += xInsertBlock->next->blocksize;
            xInsertBlock->next = xInsertBlock->next->next;
        }
        else
        {
            xInsertBlock->next = theheap.tail;
        }
    }
    getaddr = (uint8_t*)first_fitnode;
    if((getaddr + first_fitnode->blocksize) == (uint8_t*) xInsertBlock)
    {
        first_fitnode->blocksize += xInsertBlock->blocksize;
        first_fitnode->next = xInsertBlock->next;
    }
}

说实话,笔者在想直接上内存管理算法是不是有点不友好,要知道笔者当初写内存算法可是调试得死去活来,奇奇怪怪的问题不断出现。

所以为了提高大家的体验,笔者将在下一篇博客讲解如何使用gdb和keil进行调试,并且笔者将会修改内存管理算法,让它出现bug,然后用gdb去调试它,找出问题并且解决问题。

在那之后,笔者将会做一个小实验来验证我们的内存管理算法的正确性,笔者将直接替换FreeRTOS的heap4.c文件,用我们自己写的内存管理算法代替,看看FreeRTOS到底能不能跑起来!

Sparrow源码:skaiui2/SKRTOS_sparrow at source (github.com)

已经移植好的工程,包括hal库版本和标准库版本:skaiui2/SKRTOS_sparrow: Lightweight rtos inspired by SKRTOS (github.com)