brk和mmap

brk和mmap

五月 10, 2021

Linux 提供了两种堆空间分配的方式,一个是 brk() 系统调用,另一个是 mmap() 系统调用。可以使用 man brkman 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*1024Bytes 则使用 mmap。