最小可运行示例
brk() 系统调用有什么作用?
要求内核让您读取和写入称为堆的连续内存块。
如果你不问,它可能会导致你出现段错误。
没有brk
:
#define _GNU_SOURCE
#include <unistd.h>
int main(void) {
/* Get the first address beyond the end of the heap. */
void *b = sbrk(0);
int *p = (int *)b;
/* May segfault because it is outside of the heap. */
*p = 1;
return 0;
}
与brk
:
#define _GNU_SOURCE
#include <assert.h>
#include <unistd.h>
int main(void) {
void *b = sbrk(0);
int *p = (int *)b;
/* Move it 2 ints forward */
brk(p + 2);
/* Use the ints. */
*p = 1;
*(p + 1) = 2;
assert(*p == 1);
assert(*(p + 1) == 2);
/* Deallocate back. */
brk(b);
return 0;
}
GitHub 上游.
即使没有 ,上述内容也可能不会出现新页面并且不会出现段错误brk
,因此这里有一个更激进的版本,它分配 16MiB 并且很可能在没有 的情况下出现段错误brk
:
#define _GNU_SOURCE
#include <assert.h>
#include <unistd.h>
int main(void) {
void *b;
char *p, *end;
b = sbrk(0);
p = (char *)b;
end = p + 0x1000000;
brk(end);
while (p < end) {
*(p++) = 1;
}
brk(b);
return 0;
}
在 Ubuntu 18.04 上测试。
虚拟地址空间可视化
之前brk
:
+------+ <-- Heap Start == Heap End
之后brk(p + 2)
:
+------+ <-- Heap Start + 2 * sizof(int) == Heap End
| |
| You can now write your ints
| in this memory area.
| |
+------+ <-- Heap Start
之后brk(b)
:
+------+ <-- Heap Start == Heap End
为了更好地理解地址空间,您应该熟悉分页:x86 分页是如何工作的?.
为什么我们同时需要brk
and sbrk
?
brk
当然可以用sbrk
+ 偏移计算来实现,两者都只是为了方便而存在。
在后端,Linux 内核 v5.0 有一个brk
用于实现两者的系统调用:https://github.com/torvalds/linux/blob/v5.0/arch/x86/entry/syscalls/syscall_64。表#L23
12 common brk __x64_sys_brk
是brk
POSIX吗?
brk
曾经是 POSIX,但在 POSIX 2001 中被删除,因此需要_GNU_SOURCE
访问 glibc 包装器。
删除可能是由于引入mmap
,这是一个允许分配多个范围和更多分配选项的超集。
我认为没有有效的案例可以brk
代替malloc
或mmap
现在使用。
brk
对比malloc
brk
是一种旧的实施可能性malloc
。
mmap
是一种更新的、更强大的机制,可能所有 POSIX 系统当前都使用它来实现malloc
。这是一个最小的可运行mmap
内存分配示例。
我可以混合brk
和malloc吗?
如果你malloc
是用 实现的brk
,我不知道这怎么可能不会炸毁东西,因为brk
只管理一个内存范围。
但是,我在 glibc 文档中找不到任何关于它的信息,例如:
我想事情可能会在那里工作,因为mmap
可能用于malloc
.
也可以看看:
更多信息
在内部,内核决定进程是否可以拥有那么多内存,并为该使用指定内存页面。
这解释了堆栈与堆的比较:x86 汇编中寄存器上使用的 push/pop 指令的功能是什么?