glibc
根据 rootfs中的动态链接库来源 可以看到 glibc 来自于 交叉编译工具链,且glibc版本为 glibc-2.18
我们探究一下 glibc-2.18 中 mmap 以及 malloc 函数的定义以及实现
glibc 代码 在 http://ftp.gnu.org/gnu/glibc/
mmap
- 简介
mmap 是glibc提供的函数,也是系统调用
- 应用
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
mmap 函数 有个参数 flags , 可以根据 flags 将 mmap 分为四种情况
A1. 私有匿名映射 : 用于 分配大块内存(128KB及以上)
fd = -1 flags=MAP_ANONYMOUS|MAP_PRIVATE
A2. 共享匿名映射 : 用于 父子进程通信
fd = -1 flags=MAP_ANONYMOUS|MAP_SHARED
B1. 私有文件映射 : 用于 加载动态共享库
fd >=0 flags=MAP_PRIVATE
B2. 共享文件映射 : 用于 读写文件/进程间通信
fd >=0 flags=MAP_SHARED
可以这么讲,mmap就是为了读写文件存在的,读写文件流程一直在优化,在linux上,分为几个阶段
1. 直接读写磁盘 // 缺点是每次读写都要陷入内核空间,都要转动磁盘
2. 页缓存技术 // 缺点是每次读写都要陷入内核空间
3. 页缓存技术+fread/fwrite // 缺点是增加了数据在不同缓冲区复制的次数
4. mmap技术 // 优点 : 减少系统调用和内存复制的次数
- 具体实现
glibc-2.18/ports/sysdeps/unix/sysv/linux/generic/wordsize-32/mmap.c
__mmap (__ptr_t addr, size_t len, int prot, int flags, int fd, off_t offset)
return (__ptr_t) INLINE_SYSCALL (mmap2, 6, addr, len, prot, flags, fd, offset >> MMAP_PAGE_SHIFT);
weak_alias (__mmap, mmap)
linux-5.11/arch/arm/include/generated/uapi/asm/unistd-common.h
155 #define __NR_mmap2 (__NR_SYSCALL_BASE + 192)
malloc
- 简介
malloc 是glibc提供的函数,不是系统调用
底层封装的是 系统调用mmap 和 系统调用brk
- 应用
用于 申请小块内存(128KB以下,不包括)
- 实现理论
用户空间的 内存分配器
为了减少系统调用带来的开销(提高内存申请效率)
在 glibc 中实现了用户空间的内存分配器(ptmalloc/ptmalloc2)
用户申请的内存被 ptmalloc 管理 , 每一个内存块被称为 chunk,用 malloc_chunk 表示
将相同种类的 chunk 放到 链表bin中,有128个bin
128个bin 被分类为
unsorted bin
fast bins
small bins
large bins
但是如果 用户空间的 内存管理器 没有内存的话,还是要向内核申请内存
用的是系统调用,包括两种
1. brk
2. mmap
1.获取分配区的锁。
2.根据用户的需要申请的大小算出给与的大小。
3.判断所需要的大小 <= max_fast,是就跳转第5步,否则下一步
4.首先在fast bin 找,找不到就下一步,否则下一步
5.size <= 512B ,判断是否在small bin 中,如果在就下一步,否则就第6步
6.找到一个合适的chunk 大小,否则就下一步
7.到了这一步,可以看到这是一个很大size了,先遍历fast bins 合并空闲块到unsorted bin 再次合并这个块里的大小,再次比对大小信息,如果可以分配就切割大小并且将剩下的部分加入到small bin 或者 large bin 中。如果依然 < SIZE 调转下一步。
8.此时我们就表示我们从fast small unsorted 都找不到合适的块的大小,所以我们就到large bin 中寻找合适的大小。如果没有找到,就调转下一步了。
9.走到这里基本上就是说这个块非常打了,所以我们就动用TOP chunk 改变堆的大小了,如果依然不能满足需求。调转下一步。
10.这时候判断SIZE 和 mmap()分配阀值的大小。如果大于分配阀值就使用mmap()函数分配,否则调用sbrk()扩大堆的大小。
- 具体实现
glibc-2.18/misc/sbrk.c
33 __sbrk (intptr_t increment)
__brk
weak_alias (__sbrk, sbrk)
glibc-2.18/ports/sysdeps/unix/sysv/linux/arm/brk.c
__brk (void *addr)
__curbrk = newbrk = (void *) INLINE_SYSCALL (brk, 1, addr);
weak_alias (__brk, brk)
linux-5.11/arch/arm/include/generated/uapi/asm/unistd-common.h
36 #define __NR_brk (__NR_SYSCALL_BASE + 45)