unsafe/legacy 关于 brk/sbrk 是什么?

What's unsafe/legacy about brk/sbrk?

我在很多地方(musl 邮件列表、macOS 论坛等)听说 brk()sbrk() 不安全。这些地方很多要么根本不解释,要么解释的很模糊。例如,this link 声明 "these functions are fundamentally broken",然后继续说 mallocsbrk 子系统完全损坏,它们破坏了堆,等

我的问题是:为什么会这样?如果 malloc 的使用方式是分配一块 sbrk 足够大的内存来平息或大幅减少进一步分配的需要,那么 sbrkbrk 使用起来绝对安全吗?

这是我对 sbrkbrk 的实现:

sbrk:

#include <unistd.h>
#include <stddef.h>

void *sbrk(intptr_t inc)
{
        intptr_t curbrk = syscall(SYS_brk, NULL);

        if( inc == 0 ) goto ret;

        if( curbrk < 0 ) return (void *)-1;

        curbrk((void *)(curbrk+inc));
ret:
        return (void *)curbrk;
}

brk:

#include <unistd.h>

intptr_t brk(void *ptr)
{
        if( (void *)syscall(SYS_brk, ptr) != ptr )
                return -1;
        else
                return 0;
}

现实在很大程度上取决于实施,但这里有一些要素:

不安全

brk/sbrk 的发明是为了允许进程从系统请求更多内存,并在单个连续段中释放它。因此,它们被许多 mallocfree 实现使用。问题是,当它返回一个唯一的段时,当多个模块(同一进程的)直接使用它时,事情会出错。由于竞争条件,它在多线程进程中变得更糟。假设有 2 个线程要添加新内存。他们将使用 sbrk(0) 查看当前的顶部地址,看到相同的地址,使用 brksbrk 请求新内存,并且由于竞争条件,它们将使用相同的内存.

即使在单线程进程中,某些 mallocfree 实现也假设它们只被允许使用低级 s/brk 接口,并且任何其他代码都应该使用他们。在那种情况下,如果他们内部维护的中断段的图像不再是假定值,事情就会出错。他们应该猜测该段的某些部分 "reserved" 用于其他用途,可能会破坏释放任何内存的能力。

因此,用户代码不应直接使用 brk/sbrk,而应仅依赖 malloc/free。如果且仅当您正在编写标准库的实现,包括 malloc/realloc/calloc/free,您可以安全地使用 brk/ sbrk

传统

在现代系统上,mmap 可以更好地使用虚拟内存管理。您可以根据需要使用任意数量的动态内存段,它们之间没有交互。因此,在现代系统上,除非您有使用 brk/sbrk 的特定内存分配需求,否则您应该使用 mmap.

便携性

brksbrk 的 FreeBSD 参考说明如下:

The brk() and sbrk() functions are legacy interfaces from before the advent of modern virtual memory management.

及以后:

BUGS: Mixing brk() or sbrk() with malloc(3), free(3), or similar functions will result in non-portable program behavior.