根据Linux程序员手册:

Brk()和sbrk()改变程序中断的位置 定义进程数据段的结束。

这里的数据段是什么意思?它只是数据段还是数据、BSS和堆的组合?

根据维基数据段:

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

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

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


当前回答

您可以自己使用brk和sbrk来避免每个人都在抱怨的“malloc开销”。但是您不能轻易地将此方法与malloc结合使用,因此它仅适用于不需要释放任何东西的情况。因为你不能。另外,应该避免任何可能在内部使用malloc的库调用。Ie。斯特伦可能安全,但fopen可能就不安全了。

调用sbrk就像调用malloc一样。它返回一个指向当前断点的指针,并使该断点增加该值。

void *myallocate(int n){
    return sbrk(n);
}

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

void *memorypool;
void initmemorypool(void){
    memorypool = sbrk(0);
}
void resetmemorypool(void){
    brk(memorypool);
}

您甚至可以堆叠这些区域,通过将换行符倒回到区域的开始位置来丢弃最近的区域。


还有一件事……

SBRK在code golf中也很有用,因为它比malloc短2个字符。

其他回答

我可以回答你的第二个问题。Malloc将失败并返回一个空指针。这就是为什么在动态分配内存时总是检查空指针。

Malloc使用BRK系统调用来分配内存。

包括

int main(void){

char *a = malloc(10); 
return 0;
}

用strace运行这个简单的程序,它将调用BRK系统。

最小可运行示例

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和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 vs malloc

BRK是实现malloc的一种老方法。

mmap是一种更新但更强大的机制,目前所有POSIX系统都可能使用它来实现malloc。下面是一个最小可运行mmap内存分配示例。

我可以混合brk和malloc吗?

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

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

https://www.gnu.org/software/libc/manual/html_mono/libc.html#Resizing-the-Data-Segment

我想事情可能会在那里工作,因为mmap可能用于malloc。

参见:

brk/sbrk有什么不安全/遗留问题? 为什么两次调用sbrk(0)会得到不同的值?

更多信息

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

这解释了堆栈与堆的比较:在x86汇编中,寄存器上使用的推/弹出指令的功能是什么?

堆放在程序数据段的最后。Brk()用于改变(扩展)堆的大小。当堆不能再增长时,任何malloc调用都将失败。

There is a special designated anonymous private memory mapping (traditionally located just beyond the data/bss, but modern Linux will actually adjust the location with ASLR). In principle it's no better than any other mapping you could create with mmap, but Linux has some optimizations that make it possible to expand the end of this mapping (using the brk syscall) upwards with reduced locking cost relative to what mmap or mremap would incur. This makes it attractive for malloc implementations to use when implementing the main heap.