最小的可运行示例
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上游。
上面的代码即使没有,也可能不会进入新页面,也不会出现segfault brk
,因此这是分配16MiB的更具侵略性的版本,并且很可能在没有brk
:的情况下进行segfault :
#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
和sbrk
?
brk
当然可以用sbrk
+偏移量计算来实现,两者都是为了方便起见。
在后端,Linux内核v5.0具有一个brk
用于同时实现这两个功能的系统调用:https : //github.com/torvalds/linux/blob/v5.0/arch/x86/entry/syscalls/syscall_64。 tbl#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
来实现brk
,我不知道怎么可能不会炸毁东西,因为它brk
仅管理单个范围的内存。
但是我在glibc文档中找不到关于它的任何信息,例如:
我认为事情可能会在那儿工作,因为mmap
它可能用于malloc
。
也可以看看:
更多信息
在内部,内核决定进程是否可以具有那么多的内存,并为该用途指定内存页。
这就解释了堆栈与堆的比较:在x86程序集中的寄存器上使用的push / pop指令的功能是什么?