【问题标题】:How to implement calloc如何实现calloc
【发布时间】:2017-10-20 16:58:21
【问题描述】:

我正在尝试重写 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'ed 页面是零填充的。他们可能是出于安全原因,我们不希望其他进程的数据泄漏,但我找不到任何关于此的标准参考。

我们可以从/dev/zero 中使用mmap,而不是使用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()的正确方法是什么?

【问题讨论】:

  • 实现calloc() 和malloc() 的正确方法可能涉及进行适当的系统调用。细节是,perforce,系统特定的。
  • @JohnBollinger 例如哪个系统调用?
  • mmap,例如,如果分配足够大并且mmap 有一个保证零填充的选项(例如 linux 上的MAP_ANONYMOUS)。虽然我不相信这是一种常见的优化。
  • 在answer you linked 中,有一条注释写着mmap,内核总是清理内存,因为您不会将其他进程的内存留在其中(但是,这可能只能保证我认为在 linux 上)。所以在那种情况下,内存应该已经被归零了。如果它来自您的一个池,您只需要将内存归零。
  • 请注意,您的nelem * elsize 很危险,并且是安全漏洞的常见来源。您必须检查那里的溢出(关键字:__builtin_mul_overflow())

标签: c malloc standards calloc copy-on-write


【解决方案1】:

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

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

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

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

【讨论】:

  • 否则直接返回,因为已知它是新鲜的,因此已被内核清除。如果程序出现缓冲区溢出错误或写入这个尚未使用的空间的杂散指针。由于calloc() 返回的块不会完全设置为所有位为零,因此未定义的行为可能会在程序的完全不相关部分稍后发生。程序员可能会花费 很长时间 时间来调试它,并且可能会错误地得出结论:在找到真正的错误(如果有的话)之前,calloc() 是不可信的。
  • 如果有缓冲区溢出,无论如何,所有的赌注都会被取消。此外,基于堆的缓冲区溢出实际上很容易使用 valgrind 或 Address Sanitizer 检测到(也有专有工具)。基于堆栈的溢出更难检测(如果它们不会破坏返回地址或堆栈金丝雀)。
  • 你说得对,我只是指着一个现实生活中的例子,如果程序员在一个像样的系统上开发,这个例子可以用 valgrind 快速解决。取而代之的是,这个错误导致了公司范围内对 C 库的不信任,calloc() 被禁止。
  • 你确认,如果MAP_ANON 存在,我可以确定内核会给我归零内存吗?
  • 您需要阅读文档。如果有一些不知名的平台有MAP_ANON 并返回未清除的内存,我不会感到惊讶。在 Linux 上,如果您想要内核支持的未清除内存,您还必须指定 MAP_UNINITIALIZED。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-05-02
  • 2021-05-20
  • 1970-01-01
  • 2010-10-04
  • 1970-01-01
  • 2012-03-02
  • 2015-06-30
相关资源
最近更新 更多