Linux 提供了两种堆空间分配的方式,一个是 brk()
系统调用,另一个是 mmap()
系统调用。可以使用 man brk
、man mmap
查看。
brk
brk()
的声明如下:
1 2 3 4 5
| #include <unistd.h>
int brk(void *addr);
void *sbrk(intptr_t increment);
|
参数 *addr
是进程数据段的结束地址,brk()
通过改变该地址来改变数据段的大小,当结束地址向高地址移动,进程内存空间增大,当结束地址向低地址移动,进程内存空间减小。brk()
调用成功时返回 0,失败时返回 -1。 sbrk()
与 brk()
类似,但是参数 increment
表示增量,即增加或减少的空间大小,调用成功时返回增加或减小前数据段的结束地址,失败时返回 -1。
在上图中我们看到 brk 指示堆结束地址,start_brk 指示堆开始地址。BSS segment 和 heap 之间有一段 Random brk offset,这是由于 ASLR 的作用,如果关闭了 ASLR,则 Random brk offset 为 0,堆结束地址和数据段开始地址重合。
例子:
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
| #include <stdio.h> #include <unistd.h> void main() { void *curr_brk, *tmp_brk, *pre_brk;
printf("当前进程 PID:%d\n", getpid());
tmp_brk = curr_brk = sbrk(0); printf("初始化后的结束地址:%p\n", curr_brk); getchar();
brk(curr_brk+4096); curr_brk = sbrk(0); printf("brk 之后的结束地址:%p\n", curr_brk); getchar();
pre_brk = sbrk(4096); curr_brk = sbrk(0); printf("sbrk 返回值(即之前的结束地址):%p\n", pre_brk); printf("sbrk 之后的结束地址:%p\n", curr_brk); getchar();
brk(tmp_brk); curr_brk = sbrk(0); printf("恢复到初始化时的结束地址:%p\n", curr_brk); getchar(); }
|
mmap
mmap()
的声明如下:
1 2 3 4
| #include <sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
|
mmap()
函数用于创建新的虚拟内存区域,并将对象映射到这些区域中,当它不将地址空间映射到某个文件时,我们称这块空间为匿名(Anonymous)空间,匿名空间可以用来作为堆空间。mmap()
函数要求内核创建一个从地址 addr
开始的新虚拟内存区域,并将文件描述符 fildes
指定的对象的一个连续的片(chunk)映射到这个新区域。连续的对象片大小为 len
字节,从距文件开始处偏移量为 off
字节的地方开始。prot
描述虚拟内存区域的访问权限位,flags
描述被映射对象类型的位组成。
munmap()
则用于删除虚拟内存区域:
1 2 3
| #include <sys/mman.h>
int munmap(void *addr, size_t len);
|
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <stdio.h> #include <sys/mman.h> #include <unistd.h> void main() { void *curr_brk;
printf("当前进程 PID:%d\n", getpid()); printf("初始化后\n"); getchar();
char *addr; addr = mmap(NULL, (size_t)4096, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); printf("mmap 完成\n"); getchar();
munmap(addr, (size_t)4096); printf("munmap 完成\n"); getchar(); }
|
通常情况下,我们不会直接使用 brk()
和 mmap()
来分配堆空间,C 标准库提供了一个叫做 malloc
的分配器,程序通过调用 malloc()
函数来从堆中分配块,声明如下:
1 2 3 4 5 6
| #include <stdlib.h>
void *malloc(size_t size); void free(void *ptr); void *calloc(size_t nmemb, size_t size); void *realloc(void *ptr, size_t size);
|
决定使用两个函数中哪一个的条件
sysmalloc
中有这样一个判断:
1 2 3
| if (av == NULL || ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold) && (mp_.n_mmaps < mp_.n_mmaps_max)))
|
满足条件则使用mmap
,否则使用brk
,关键在于申请的 size 是否大于mmap_threshold
,对这个值的定义如下:
1 2 3
| #define DEFAULT_MMAP_THRESHOLD_MIN (128 * 1024) #define DEFAULT_MMAP_THRESHOLD DEFAULT_MMAP_THRESHOLD_MIN .mmap_threshold = DEFAULT_MMAP_THRESHOLD,
|
size 大于128*1024
Bytes 则使用 mmap。