首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >brk()系统调用做什么?

brk()系统调用做什么?
EN

Stack Overflow用户
提问于 2011-08-08 20:57:32
回答 8查看 116.3K关注 0票数 234

根据Linux程序员手册:

brk()和sbrk()更改了程序中断的位置,这定义了进程数据段的结束。

数据段在这里意味着什么?它只是数据段还是数据、BSS和堆的结合?

根据wiki 数据段

有时,数据、BSS和堆区域统称为“数据段”。

我认为没有理由改变数据段的大小。如果是数据、BSS和堆,那么它就有意义了,因为堆将获得更多的空间。

这就引出了我的第二个问题。在我所读到的所有文章中,作者说堆向上增长,堆栈向下增长。但是,他们没有解释的是,当堆占据堆和堆栈之间的所有空间时,会发生什么?

EN

回答 8

Stack Overflow用户

回答已采纳

发布于 2011-08-09 01:19:37

在您发布的图表中,-the地址是由brk操作的,sbrk-is是堆顶部的虚线。

您所阅读的文档将此描述为“数据段”的结束,因为在传统的(预共享库,预mmap) Unix中,数据段与堆是连续的;在程序启动之前,内核将将“文本”和“数据”块加载到从地址零开始的RAM中(实际上略高于地址零,因此空指针真正没有指向任何东西),并将中断地址设置为数据段的末尾。然后,对malloc的第一次调用将使用sbrk移动碎片,并在数据段顶部和新的、更高的中断地址之间创建堆,如图所示,随后使用malloc将使堆在必要时更大。

同时,堆栈从内存的顶部开始并向下扩展。堆栈不需要显式的系统调用来使其更大;要么它开始时分配给它的RAM尽可能多(这是传统的方法),要么堆栈下面有一个预留地址区域,当内核注意到试图写入它时,它会自动分配RAM (这是现代的方法)。无论哪种方式,在地址空间的底部都有一个可以用于堆栈的“保护”区域,也可能没有。如果存在此区域(所有现代系统都会这样做),则将永久取消映射;如果堆栈或堆试图发展为该区域,则会出现分段错误。但是,传统上内核并不试图强制执行边界;堆栈可以发展为堆,或者堆可以发展为堆栈,不管是哪种方式,它们都会在彼此的数据上乱写,程序就会崩溃。如果你很幸运的话,它就会立即坠毁。

我不知道这个图表中的512‘m是从哪里来的。它意味着一个64位的虚拟地址空间,这与您拥有的非常简单的内存映射不一致。一个真正的64位地址空间看起来更像这样:

代码语言:javascript
复制
              Legend:  t: text, d: data, b: BSS

这不是远程扩展的,也不应该被解释为任何给定的操作系统都是如何工作的(在我绘制它之后,我发现Linux实际上使可执行文件的地址比我想象的更接近零,并且共享库的地址高得惊人)。这个图中的黑色区域没有映射--任何访问都会导致一个直接的分段故障--相对于灰色区域,它们是巨大的。浅灰区域是程序及其共享库(可以有几十个共享库);每个库都有一个独立的文本和数据段(和"bss“段,其中也包含全局数据,但被初始化为所有位-零,而不是占用磁盘上可执行文件或库中的空间)。堆不再一定要连续使用可执行文件的数据段--我是这样画的,但是看起来至少Linux不这样做。堆栈不再固定在虚拟地址空间的顶部,堆与堆栈之间的距离非常大,因此不必担心跨越它。

断点仍然是堆的上限。然而,我没有显示的是,可能有几十个独立的内存分配在那里的某个地方,用mmap而不是brk制作的。(操作系统将试图将这些远离brk区域,这样它们就不会发生碰撞。)

票数 288
EN

Stack Overflow用户

发布于 2015-06-26 21:23:28

最小可运行示例

brk( )系统调用做什么?

请求内核允许您读取并写入称为堆的连续内存块。

如果你不问的话,它可能会把你分出来。

brk

代码语言:javascript
复制
#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

代码语言:javascript
复制
#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,上面可能也不会触及一个新页面,而不是分段错误,因此这里有一个更激进的版本,它分配16 hit,并且很可能在没有brk的情况下进行分段错误。

代码语言:javascript
复制
#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之前

代码语言:javascript
复制
+------+ <-- Heap Start == Heap End

brk(p + 2)

代码语言:javascript
复制
+------+ <-- Heap Start + 2 * sizof(int) == Heap End 
|      |
| You can now write your ints
| in this memory area.
|      |
+------+ <-- Heap Start

brk(b)

代码语言:javascript
复制
+------+ <-- Heap Start == Heap End

为了更好地理解地址空间,您应该熟悉分页:x86分页是如何工作的?

为什么我们同时需要brk sbrk**?**?

当然,brk可以用sbrk +偏移量计算来实现,两者都是为了方便而存在的。

在后端,Linux内核v5.0有一个单独的系统调用brk,用于实现这两者:64.tbl#L23

代码语言:javascript
复制
12  common  brk         __x64_sys_brk

brk POSIX?

brk过去是POSIX,但它在POSIX 2001中被删除,因此_GNU_SOURCE需要访问glibc包装器。

删除可能是由于引入了mmap,它是一个超集,允许分配多个范围和更多的分配选项。

我认为,现在您应该使用brk而不是mallocmmap,这是没有道理的。

brk malloc

brk是实现malloc的一种老可能性。

mmap是一种新的、功能更强大的机制,很可能所有POSIX系统目前都使用它来实现malloc。这是一个内存分配示例

我能把brk 和malloc混在一起吗?

如果您的malloc是用brk实现的,我不知道这怎么可能不会炸掉东西,因为brk只管理一个内存范围。

然而,我在glibc文档中找不到任何关于它的信息,例如:

我认为,由于mmap可能用于malloc,所以可能只在那里工作。

另请参阅:

更多信息

在内部,内核决定进程是否可以拥有那么多内存,并为这种使用指定内存页

这说明了堆栈与堆:在x86程序集中的寄存器上使用的push / pop指令的功能是什么?的比较。

票数 43
EN

Stack Overflow用户

发布于 2011-08-09 06:10:49

您可以自己使用brksbrk来避免每个人总是抱怨的"malloc开销“。但是,您不能轻易地在malloc中使用此方法,因此只有在不必使用free的情况下才适合使用该方法。而且,您应该避免任何可能在内部使用malloc的库调用。即。strlen可能是安全的,但fopen可能不安全。

打电话给sbrk,就像你打给malloc一样。它返回一个指向当前中断的指针,并将该值增加一个值。

代码语言:javascript
复制
void *myallocate(int n){
    return sbrk(n);
}

虽然您不能释放单个分配(因为没有malloc开销,请记住),但是您可以通过调用brk来释放整个空间,第一次调用sbrk返回的值,从而使brk返回。

代码语言:javascript
复制
void *memorypool;
void initmemorypool(void){
    memorypool = sbrk(0);
}
void resetmemorypool(void){
    brk(memorypool);
}

您甚至可以堆叠这些区域,通过将中断退到区域的起始位置来丢弃最近的区域。

还有一件事。

sbrk电码高尔夫中也很有用,因为它比malloc短2个字符。

票数 10
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/6988487

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档