skaiuijing
上一篇笔者讲了内存分配的算法,是适合让我们一步步实现这些算法了,一共有四个函数,分别是初始化、分配、释放、插入。
初始化
初始化后的内存分布如图所示:
1 2 3 4 5
| graph LR A(theheap)--->D A-->C D(头部)--相邻-->B(内存块)--相邻-->C(尾部)
|
简单来说,就是我们要在大内存数组起始地址和终结地址分别创建两个heap_node节点,用于管理这个内存块,然后将它们使用指针连起来,还记得上一篇博客中我们定义的theheap吗?它负责记录头部和尾部。
像&= ~这些操作是字节对齐,字节对齐可以提高CPU运行速度,也可以防止error的发生
另外还要给读者介绍一点小知识:对于正整数X,Y = 2^n,X%Y 可以被X&(Y-1)代替。
举个例子,X % 16 = X &15。
证明过程看笔者博客:c程序杂谈系列(加减乘除模篇)-CSDN博客
程序如下:此时,firstnode就是头部。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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算法:
1 2 3 4 5
| graph LR Aa(头部)--从头开始找_直到找到符合大小的内存块-->Ba(内存块)-->Ca(内存块)-->Da(内存块)-->Ea(内存块)-->Ta(尾部) G(链表起始)--空闲内存块链表-->F(结束) A(prev)--在这条链表上找到符合条件的内存块-->B(use)-->C(new) Ap(prev)--如果分配后有剩余_就分割-->ap(分割下来的内存块)-->Cp(new)
|
在初始化后,空闲内存块已经形成了一条链表,这条链表被称为空闲内存块链表,内存块被使用就被踢出这条链表,被释放就加入这条链表,如果内存块大于要申请的大小,就把多余的部分切割,然后加入这条内存链表。
源码,malloc会找到合适的内存块,并且返回它的地址:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| 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 - (wantsize & aligment_byte); 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; }
|
释放
当有内存块需要释放时,直接插入空闲内存块链表即可即可。
插入前:
1 2 3 4
| graph LR A(空闲内存块链表节点)-->D(空闲内存块链表节点)-->B(空闲内存块链表节点);
C(Insert)-->D(空闲内存块);
|
插入后:
1 2 3 4
| graph LR A(空闲内存块链表节点)-->C(Insert节点)-->D(空闲内存块链表节点)-->B(空闲内存块链表节点);
|
程序如下:
1 2 3 4 5 6 7 8 9 10 11
| 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释放的内存块的地址与其他空闲内存块是连续的,我们会合并它们,这里是内存管理算法的精髓所在。
1 2 3 4 5
| graph LR A(空闲内存块链表)-->B(prev)-->C(next)-->D(结束); E(Insert)--寻找插入的位置-->G(?)
|
1 2 3 4
| graph LR A(start)-->B-->C(Insert)-->D-->E-->G(end);
|
后相邻插入合并:
1 2 3
| graph LR A(start)--后相邻-->B-->C(Insert)-->E-->F(end); D-->E;
|
等价于:
1 2 3
| graph LR A(start)--后相邻-->B-->C(Insert + D)-->E-->F(end);
|
前相邻插入合并:
1 2 3 4
| graph LR
A(start)--前相邻-->B-->D-->E-->F(end); C(Insert)-->D;
|
等价于:
1 2 3 4
| graph LR
A(start)--前相邻-->B(B+Insert)-->D-->E-->F(end);
|
前+后:注:B直接指向E
1 2
| graph LR A(start)--可以只考虑最终结果-->B--Insert + D-->E-->F(end);
|
等价于:
1 2 3 4
| graph LR
A(start)--可以只考虑最终结果-->B(B + Insert +D)-->E-->F(end);
|
如果前后相邻是相互独立的,那么互不影响,如果不独立,通过后,前的顺序可以得到前后都相邻的结果,因此该顺序正确。
程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 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)