“这解释了我所看到的,但我有几个问题:”
“有没有办法检测某些东西是否已经映射到某个地址?无需访问 /proc/maps?”
是的,使用不带 MAP_FIXED 的 mmap。
“有没有办法在找到重叠页面的情况下强制mmap失败?”
显然不是,但如果 mmap 返回所请求地址以外的映射,则只需在 mmap 之后使用 munmap。
当在没有MAP_FIXED 的情况下使用时,Linux 和 Mac OS X 上的 mmap(我也怀疑在其他地方)服从地址参数,如果在 [地址,地址 + 长度)范围内不存在现有映射。因此,如果 mmap 回答与您提供的地址不同的地址的映射,您可以推断该范围内已经存在映射,您需要使用不同的范围。由于 mmap 通常会在忽略地址参数时回答位于非常高地址的映射,因此只需使用 munmap 取消映射该区域,然后在不同的地址重试。
使用 mincore 检查地址范围的使用不仅浪费时间(必须一次探测一个页面),它可能不起作用。较旧的 linux 内核只会使 mincore 因文件映射而失败。对于 MAP_ANON 映射,他们根本不会回答任何问题。但正如我所指出的,您所需要的只是 mmap 和 munmap。
我刚刚完成了这个为 Smalltalk VM 实现内存管理器的练习。我使用 sbrk(0) 找出可以映射第一个段的第一个地址,然后使用 mmap 和 1Mb 的增量为后续段搜索空间:
static long pageSize = 0;
static unsigned long pageMask = 0;
#define roundDownToPage(v) ((v)&pageMask)
#define roundUpToPage(v) (((v)+pageSize-1)&pageMask)
void *
sqAllocateMemory(usqInt minHeapSize, usqInt desiredHeapSize)
{
char *hint, *address, *alloc;
unsigned long alignment, allocBytes;
if (pageSize) {
fprintf(stderr, "sqAllocateMemory: already called\n");
exit(1);
}
pageSize = getpagesize();
pageMask = ~(pageSize - 1);
hint = sbrk(0); /* the first unmapped address above existing data */
alignment = max(pageSize,1024*1024);
address = (char *)(((usqInt)hint + alignment - 1) & ~(alignment - 1));
alloc = sqAllocateMemorySegmentOfSizeAboveAllocatedSizeInto
(roundUpToPage(desiredHeapSize), address, &allocBytes);
if (!alloc) {
fprintf(stderr, "sqAllocateMemory: initial alloc failed!\n");
exit(errno);
}
return (usqInt)alloc;
}
/* Allocate a region of memory of at least size bytes, at or above minAddress.
* If the attempt fails, answer null. If the attempt succeeds, answer the
* start of the region and assign its size through allocatedSizePointer.
*/
void *
sqAllocateMemorySegmentOfSizeAboveAllocatedSizeInto(sqInt size, void *minAddress, sqInt *allocatedSizePointer)
{
char *address, *alloc;
long bytes, delta;
address = (char *)roundUpToPage((unsigned long)minAddress);
bytes = roundUpToPage(size);
delta = max(pageSize,1024*1024);
while ((unsigned long)(address + bytes) > (unsigned long)address) {
alloc = mmap(address, bytes, PROT_READ | PROT_WRITE,
MAP_ANON | MAP_PRIVATE, -1, 0);
if (alloc == MAP_FAILED) {
perror("sqAllocateMemorySegmentOfSizeAboveAllocatedSizeInto mmap");
return 0;
}
/* is the mapping both at or above address and not too far above address? */
if (alloc >= address && alloc <= address + delta) {
*allocatedSizePointer = bytes;
return alloc;
}
/* mmap answered a mapping well away from where Spur prefers. Discard
* the mapping and try again delta higher.
*/
if (munmap(alloc, bytes) != 0)
perror("sqAllocateMemorySegment... munmap");
address += delta;
}
return 0;
}
这似乎运作良好,在跳过任何现有映射的同时在升序地址分配内存。
高温高压