1. 前言
glibc的malloc函数在申请大于128K的内存时使用mmap
分配内存,mmap会从堆区和栈区中间的部分划分内存,而在申请小于128K的内存时使用brk
从堆上划分内存。
2. brk/sbrk
brk是linux上一个系统调用,而sbrk是一个C库函数
2.1 brk函数原型
代码语言:javascript复制int brk(void *addr);
参数
参数 | 解释 |
---|---|
addr | 要调整到的内存地址 |
返回值
返回增加的大小
2.2 sbrk函数原型
代码语言:javascript复制void *sbrk(intptr_t increment);
参数
参数 | 解释 |
---|---|
increment | 增加的内存大小 |
返回值
返回增加之后的program break
内存地址
2.3 brk的原理
brk
实际是通过改变program break
来实现的,这个program break
可以理解为“堆顶指针”,意味着程序堆内存使用到了该位置。
而这种内存的分配方式有个问题,看下下图:
假设B已经被free
了,但是由于上面有一个C对象,所以program break
指针不能简单的向下移动来释放内存。那怎么办呢?
实际上free
后不能立即归还内存,只是将这块内存标记为空闲,后续再申请内存时可以复用这块内存。并且两块连续的空闲内存可以合并为一块空闲内存,当最高地址空间的空闲内存大于128K时(可以通过M_TRIM_THRESHOLD选项调节),执行内存紧缩。对于上图来说,如果C也被free
了,program break
就可以指向A的结束地址了,就能释放一部分内存了。
难道这就是传说中的线性内存分配
3. mmap
3.1 mmap函数原型
代码语言:javascript复制void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
参数
参数 | 解释 |
---|---|
addr | 映射的起始地址,通常设置为NULL,由内核来分配 |
len | 指定将文件映射到内存的部分的长度 |
prot | 映射区域的保护方式,通常是下面几个选项的组合 |
flags | 映射区的特性标志位,常用的有以下两个选项 |
fd | 要映射到内存中的文件描述符 |
offset | 文件映射的偏移量,必须是操作系统内存页大小的整数倍 |
返回值
返回映射区的起始地址
3.2 munmap函数原型
代码语言:javascript复制// 解除映射
int munmap(void *start, size_t length);
参数
参数 | 解释 |
---|---|
start | mmap返回的映射区的起始地址 |
length | 映射区的大小 |
返回值
解除成功返回0,失败返回-1
3.3 mmap原理
linxu内核使用vm_area_struct
结构来表示一个独立的虚拟内存区域(比如堆、栈、bss段、代码段等),一个进程会有多个vm_area_struct
结构体,各个vm_area_struct
使用树结构或者链表连接。
vm_area_struct
结构包含了该区域的起止地址和其他信息以及一个vm_ops
指针,vm_ops
指针内部引出所有针对这个区域可用的系统调用函数。
mmap
就是创建一个新vm_area_struct
结构,并将其与文件磁盘地址做映射。
mmap
申请的内存可以通过munmap
释放。
4. 总结
方式 | 内存碎片 | 适用场景 |
---|---|---|
brk | 多,因为不可释放 | 申请小内存 |
mmap | 无,因为可以直接释放 | 申请大内存,如果用来申请小内存的话就会创建非常多的vm_area_struct结构体,不划算 |
5. 参考资料
- https://www.cnblogs.com/huxiao-tee/p/4660352.html
- https://blog.csdn.net/yusiguyuan/article/details/39496057