从malloc中窥探Linux内存分配策略
malloc函数是C/C++中常用内存分配库函数,本篇文章将以Linux平台上的malloc为剖析对象,深入了解分配一块内存的旅程。
malloc入门
使用malloc,需要包含头文件 stdlib.h ,函数原型如下:
extern void *malloc(unsigned int num_bytes);
功能: 分配长度为 num_bytes的内存块,如果分配成功,则返回指向被分配内存的指针,否则返回空指针NULL,否则发生的情况,一般为系统堆上可用的内存上无法找到一块长度大于num_bytes的连续内存空间。
特别情况:如果num_bytes为0,malloc成功分配0字节的空间,返回一个有效指针,但无法使用此指针。
当内存不再使用时,应使用free函数将内存块释放。
返回值类型为void*,表示未确定类型的指针,它可以强制转换为任何其他类型的指针。
extern free(void *FirstByte);
功能: 将之前malloc分配的空间还给操作系统,释放传入指针指向的那块内存区域,指针本身的数值没变,释放前,指向的内容是可理解的,释放后,指向的内容是垃圾内容。
注意: 释放空指针,不会出错。释放同一个有效指针两次,会出错。释放后,最好把指向这块内存的指针指向NULL,防止后面的程序误用。
malloc深入了解
glibc库实现了malloc,它实现linux系统的堆管理。在Linux中,大部分的系统调用都是通过C库函数来体现了,因此glibc就显得尤为重要。glibc的实现策略和Windows的类似,都是维护一个全局链表,每个链表元素由不定长的内存块链表。
与Windows不同的是,在glibc中,维护了多个不定长的内存块链表,每一个链表负责一个大小范围,这种做法有效减少了分配大内存时的遍历开销,类似于哈希的方式,将很大的范围的数据散列到有限的几个小的范围内而不是所有数据都放在一起,虽然最终还是要在小的范围内查找,但是最起码省去了很多的开销,如果只有一个不定长链表那么就要全部遍历,如果分成3个,就省去了2/3的开销,总之这个策略十分类似于散列。glibc另外的策略就是不止维护一类空闲链表,而是另外再维护一个缓冲链表和一个高速缓冲链表,在分配的时候首先在高速缓存中查找,失败之后再在空闲链表查找,如果找到的内存块比较大,那么将切割之后的剩余内存块插入到缓存链表,如果空闲链表查找失败那么就往缓存链表中查找. 如果还是没有合适的空闲块,就向内存申请比请求数更大的内存块,然后把剩下的内存放入链表中。这种方式是glibc自己实现的策略。
malloc函数,它内部有一个将多个可用内存块连接为一个空闲链表。在调用时,它沿链表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到链接表上。
调用free函数时,它将用户释放的内存块回收到空闲链表。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。
《UNIX环境高级编程》第七章:
从glibc中malloc的详细解释一文中知道:
在glibc的malloc实现中,分配虚存有两种系统调用可用,brk和mmap2,根据默认门限值来决定具体调用哪个进行分配。
malloc返回的每块内存的起始处首先要有这个结构:
内存控制块结构定义
struct mem_control_block {
int is_available;
int size;
};
free函数比较简单,给出来。
4. 解除分配函数 free void free(void *firstbyte) { struct mem_control_block *mcb; /* Backup from the given pointer to find the * mem_control_block */ mcb = firstbyte - sizeof(struct mem_control_block); /* Mark the block as being available */ mcb->is_available = 1; /* That''s It! We''re done. */ return; }
free函数中的第二句话,将传入指针倒回去内存控制块的大小,然后将该块中的is_available设置为1,将此空间标记为空闲空间,至于后续的回收整理,应该是操作系统和glibc内存管理模块来整理。从实现上看,释放的传入只能是分配内存块的首地址,而不能是中间的某个地址。
从上述的free代码中来看,执行free函数后,堆上对应已分配内存已经标记为可用,但是这个可用内存空间经过多久才能被用户再次使用,这就取决于OS的内存管理策略,在还没将此块空间回收整理到空闲链表前,这块空间对用户来说,是不可用的。
参考资料:malloc和free的实现原理