我正在尝试重写malloc和calloc,我的问题是关于calloc的实现,而不是如何使用它。
应该始终使用calloc()
而不是malloc()+memset()
,因为它可以利用写时复制(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
的页面为零填充。它们可能是出于安全原因,我们不希望其他进程的数据泄露,但我找不到任何关于这方面的标准参考。
代替使用MAP_ANON
,我们可以从/dev/zero
:中获得mmap
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()
的正确方法是什么?
Pure POSIX不支持匿名内存映射,并且没有比calloc
更低级别的接口来分配零内存。
现有的POSIX实现支持匿名私有内存映射作为扩展,通过MAP_ANON
或MAP_ANONYMOUS
标志(或历史上通过从/dev/zero
映射)。内核确保应用程序只看到零内存。(也有较旧的接口,如brk
和sbrk
,但它们很难使用非线程安全的接口。)
malloc
函数族的实现通常使用mmap
来分配较大的块,并且为每个块保留水印指针,该水印指针指示哪个部分已经被分配给应用程序至少一次(经由malloc
/realloc
/calloc
,这无关紧要)。calloc
在返回分配之前会检查水印指针,如果应用程序以前使用过内存,它会将其清除。否则,它会直接返回,因为它是新的,因此已被内核清除。
也可以使用mmap
直接分配大的块。但是内核最终也必须清除内存(在使用它来支持触发写时复制错误的映射之前),所以只有当分配比实际需要大得多,并且大多数部分从未写入时,这才是一个明显的胜利