如何实现calloc

How to implement calloc

我正在尝试重写malloc和calloc,我的问题是关于calloc的实现,而不是如何使用它。

应该始终使用 calloc() 而不是 malloc()+memset(),因为它可以利用 copy-on-write(COW)。

有些calloc是这样实现的:

void * calloc(size_t nelem, size_t elsize)
{
    void *p;

    p = malloc (nelem * elsize);
    if (p == 0)
        return (p);

    bzero (p, nelem * elsize);
    return (p);
}

但他们根本不使用 COW(并且他们不检查溢出)。

如果这些实现不调用 bzero(),它们必须假定它们收到的 mmap 页面是零填充的。他们可能是出于安全原因,我们不希望其他进程的数据泄漏,但我找不到任何关于此的标准参考。

我们可以 mmap 来自 /dev/zero:

而不是使用 MAP_ANON
fd = open("/dev/zero", O_RDWR); 
a = mmap (0, 4096e4, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FILE, fd, 0);

但是 /dev/zero 没有被 POSIX 强制要求,并且可以很容易地做到 sudo mv /dev/zero /dev/foo,破坏了我的实现。

关于写时复制,有效重写 calloc() 的正确方法是什么?

纯 POSIX 不支持匿名内存映射,并且没有比 calloc 更低级别的接口来分配零内存。

现有 POSIX 实现支持匿名私有内存映射作为扩展,通过 MAP_ANONMAP_ANONYMOUS 标志(或历史上,通过从 /dev/zero 映射)。内核确保应用程序只看到归零的内存。 (也有一些较旧的接口,例如 brksbrk,但它们很难使用并且不是线程安全的。)

malloc 函数族的实现通常使用 mmap 分配更大的块,并为每个块保留一个水印指针,指示哪个部分已经至少分配给应用程序一次(通过 malloc/realloc/calloc,无关紧要)。 calloc 在返回分配之前检查水印指针,如果之前应用程序使用过内存,则将其清除。否则直接返回,因为知道是新鲜的,已经被内核清除了。

大块也可以直接使用mmap分配。但是内核最终也必须清除内存(在将其用于触发写时复制错误的反向映射之前),所以如果分配比实际需要的大得多,那么这只是一个明显的胜利,而且大部分都是从未写过。