由于与此问题无关的一些模糊原因,我需要使用MAP_FIXED来获取靠近libc的文本部分存储在内存中的页面。
在阅读mmap(2)之前(我本来应该做的),如果我用MAP_FIXED调用mmap并且基本地址与已经映射的区域重叠,那么我期待得到一个错误。
然而事实并非如此。例如,这是某些进程的/ proc / maps的一部分
7ffff7299000-7ffff744c000 r-xp 00000000 08:05 654098 /lib/x86_64-linux-gnu/libc-2.15.so
在进行以下mmap调用后...
mmap(0x7ffff731b000,
getpagesize(),
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED,
0,
0);
......变成:
7ffff7299000-7ffff731b000 r-xp 00000000 08:05 654098 /lib/x86_64-linux-gnu/libc-2.15.so
7ffff731b000-7ffff731c000 rwxp 00000000 00:00 0
7ffff731c000-7ffff744c000 r-xp 00083000 08:05 654098 /lib/x86_64-linux-gnu/libc-2.15.so
这意味着我用自己的页面覆盖了专用于libc的部分虚拟地址空间。显然不是我想要的......
在mmap(2)手册的MAP_FIXED部分,它清楚地说明了:
如果addr和len指定的内存区域与任何页面重叠 现有的映射,然后现有映射的重叠部分将被丢弃。
这解释了我所看到的内容,但我有几个问题:
答案 0 :(得分:6)
使用page = sysconf(SC_PAGE_SIZE)
查找页面大小,然后使用msync(addr, page, 0)
((unsigned long)addr % page == 0
扫描您要检查的每个页面大小的块,即addr
与页面对齐)。如果它返回-1
errno == ENOMEM
,则不会映射该页面。
编辑:如下文所述,mincore(addr,page,&dummy)
优于msync()
。 (系统调用的实现位于Linux内核源代码的mm/mincore.c
中,C库通常提供更新errno
的包装。由于系统调用在确认addr
后立即进行映射检查是页面对齐的,在非映射的情况下(ENOMEM
)是最佳的。如果页面已经映射,它会起作用,所以如果性能是最重要的,请尽量避免检查你知道映射的页面。
您必须单独为每个页面单独执行此操作,因为对于大于单个页面的区域,ENOMEM
表示该区域未完全映射;它可能仍然是部分映射的。映射总是精确到页面大小的单位。
据我所知,如果区域已经映射,或者包含已映射的页面,则无法告诉mmap()
失败。 (这同样适用于mremap()
,因此您无法创建映射,然后将其移动到所需的区域。)
这意味着您面临竞争风险。最好自己执行实际的系统调用,而不是C库包装器,以防它们在内部进行内存分配或更改内存映射:
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
static size_t page = 0;
static inline size_t page_size(void)
{
if (!page)
page = (size_t)sysconf(_SC_PAGESIZE);
return page;
}
static inline int raw_msync(void *addr, size_t length, int flags)
{
return syscall(SYS_msync, addr, length, flags);
}
static inline void *raw_mmap(void *addr, size_t length, int prot, int flags)
{
return (void *)syscall(SYS_mmap, addr, length, prot, flags, -1, (off_t)0);
}
但是,我怀疑无论你想做什么,最终都需要解析/proc/self/maps
。
我建议完全避免使用标准I / O stdio.h
(因为各种操作会动态分配内存,从而更改映射),而是使用较低级别的unistd.h
接口,这不太可能影响映射。这是一组简单粗略的函数,您可以使用它们找出每个映射区域以及该区域中启用的保护(并丢弃其他信息)。实际上,它使用大约一千字节的代码而不是堆栈中的代码,所以即使在有限的架构(例如嵌入式设备)上它也非常有用。
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#ifndef INPUT_BUFFER
#define INPUT_BUFFER 512
#endif /* INPUT_BUFFER */
#ifndef INPUT_EOF
#define INPUT_EOF -256
#endif /* INPUT_EOF */
#define PERM_PRIVATE 16
#define PERM_SHARED 8
#define PERM_READ 4
#define PERM_WRITE 2
#define PERM_EXEC 1
typedef struct {
int descriptor;
int status;
unsigned char *next;
unsigned char *ends;
unsigned char buffer[INPUT_BUFFER + 16];
} input_buffer;
/* Refill input buffer. Returns the number of new bytes.
* Sets status to ENODATA at EOF.
*/
static size_t input_refill(input_buffer *const input)
{
ssize_t n;
if (input->status)
return (size_t)0;
if (input->next > input->buffer) {
if (input->ends > input->next) {
memmove(input->buffer, input->next,
(size_t)(input->ends - input->next));
input->ends = input->buffer + (size_t)(input->ends - input->next);
input->next = input->buffer;
} else {
input->ends = input->buffer;
input->next = input->buffer;
}
}
do {
n = read(input->descriptor, input->ends,
INPUT_BUFFER - (size_t)(input->ends - input->buffer));
} while (n == (ssize_t)-1 && errno == EINTR);
if (n > (ssize_t)0) {
input->ends += n;
return (size_t)n;
} else
if (n == (ssize_t)0) {
input->status = ENODATA;
return (size_t)0;
}
if (n == (ssize_t)-1)
input->status = errno;
else
input->status = EIO;
return (size_t)0;
}
/* Low-lever getchar() equivalent.
*/
static inline int input_next(input_buffer *const input)
{
if (input->next < input->ends)
return *(input->next++);
else
if (input_refill(input) > 0)
return *(input->next++);
else
return INPUT_EOF;
}
/* Low-level ungetc() equivalent.
*/
static inline int input_back(input_buffer *const input, const int c)
{
if (c < 0 || c > 255)
return INPUT_EOF;
else
if (input->next > input->buffer)
return *(--input->next) = c;
else
if (input->ends >= input->buffer + sizeof input->buffer)
return INPUT_EOF;
memmove(input->next + 1, input->next, (size_t)(input->ends - input->next));
input->ends++;
return *(input->next) = c;
}
/* Low-level fopen() equivalent.
*/
static int input_open(input_buffer *const input, const char *const filename)
{
if (!input)
return errno = EINVAL;
input->descriptor = -1;
input->status = 0;
input->next = input->buffer;
input->ends = input->buffer;
if (!filename || !*filename)
return errno = input->status = EINVAL;
do {
input->descriptor = open(filename, O_RDONLY | O_NOCTTY);
} while (input->descriptor == -1 && errno == EINTR);
if (input->descriptor == -1)
return input->status = errno;
return 0;
}
/* Low-level fclose() equivalent.
*/
static int input_close(input_buffer *const input)
{
int result;
if (!input)
return errno = EINVAL;
/* EOF is not an error; we use ENODATA for that. */
if (input->status == ENODATA)
input->status = 0;
if (input->descriptor != -1) {
do {
result = close(input->descriptor);
} while (result == -1 && errno == EINTR);
if (result == -1 && !input->status)
input->status = errno;
}
input->descriptor = -1;
input->next = input->buffer;
input->ends = input->buffer;
return errno = input->status;
}
/* Read /proc/self/maps, and fill in the arrays corresponding to the fields.
* The function will return the number of mappings, even if not all are saved.
*/
size_t read_maps(size_t const n,
void **const ptr, size_t *const len,
unsigned char *const mode)
{
input_buffer input;
size_t i = 0;
unsigned long curr_start, curr_end;
unsigned char curr_mode;
int c;
errno = 0;
if (input_open(&input, "/proc/self/maps"))
return (size_t)0; /* errno already set. */
c = input_next(&input);
while (c >= 0) {
/* Skip leading controls and whitespace */
while (c >= 0 && c <= 32)
c = input_next(&input);
/* EOF? */
if (c < 0)
break;
curr_start = 0UL;
curr_end = 0UL;
curr_mode = 0U;
/* Start of address range. */
while (1)
if (c >= '0' && c <= '9') {
curr_start = (16UL * curr_start) + c - '0';
c = input_next(&input);
} else
if (c >= 'A' && c <= 'F') {
curr_start = (16UL * curr_start) + c - 'A' + 10;
c = input_next(&input);
} else
if (c >= 'a' && c <= 'f') {
curr_start = (16UL * curr_start) + c - 'a' + 10;
c = input_next(&input);
} else
break;
if (c == '-')
c = input_next(&input);
else {
errno = EIO;
return (size_t)0;
}
/* End of address range. */
while (1)
if (c >= '0' && c <= '9') {
curr_end = (16UL * curr_end) + c - '0';
c = input_next(&input);
} else
if (c >= 'A' && c <= 'F') {
curr_end = (16UL * curr_end) + c - 'A' + 10;
c = input_next(&input);
} else
if (c >= 'a' && c <= 'f') {
curr_end = (16UL * curr_end) + c - 'a' + 10;
c = input_next(&input);
} else
break;
if (c == ' ')
c = input_next(&input);
else {
errno = EIO;
return (size_t)0;
}
/* Permissions. */
while (1)
if (c == 'r') {
curr_mode |= PERM_READ;
c = input_next(&input);
} else
if (c == 'w') {
curr_mode |= PERM_WRITE;
c = input_next(&input);
} else
if (c == 'x') {
curr_mode |= PERM_EXEC;
c = input_next(&input);
} else
if (c == 's') {
curr_mode |= PERM_SHARED;
c = input_next(&input);
} else
if (c == 'p') {
curr_mode |= PERM_PRIVATE;
c = input_next(&input);
} else
if (c == '-') {
c = input_next(&input);
} else
break;
if (c == ' ')
c = input_next(&input);
else {
errno = EIO;
return (size_t)0;
}
/* Skip the rest of the line. */
while (c >= 0 && c != '\n')
c = input_next(&input);
/* Add to arrays, if possible. */
if (i < n) {
if (ptr) ptr[i] = (void *)curr_start;
if (len) len[i] = (size_t)(curr_end - curr_start);
if (mode) mode[i] = curr_mode;
}
i++;
}
if (input_close(&input))
return (size_t)0; /* errno already set. */
errno = 0;
return i;
}
read_maps()
函数最多可读取n
个区域,将void *
的地址作为ptr
数组,len
数组的长度以及权限mode
数组,返回地图总数(可能大于n
),如果发生错误,则设置为errno
为零。
很可能将syscall用于上面的低级I / O,因此您不使用任何C库功能,但我认为根本不需要。 (就我所知,C库使用围绕实际系统调用的非常简单的包装器。)
我希望你觉得这很有用。
答案 1 :(得分:6)
&#34;这解释了我所看到的内容,但我有几个问题:&#34;
&#34;有没有办法检测某些东西是否已经映射到某个地址?没有访问/ proc / maps?&#34;
是的,使用没有MAP_FIXED的mmap。
&#34;在找到重叠页面的情况下,有没有办法强制mmap失败?&#34;
显然不是,但只是在mmap之后使用munmap,如果mmap返回的映射不是请求的地址。
当使用而没有 MAP_FIXED时,linux和Mac OS X上的mmap(我也怀疑其他地方)如果存在[address,address + length]范围内的现有映射,则服从地址参数。因此,如果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;
}
这似乎运行良好,在跳过任何现有映射的同时在升序地址分配内存。
HTH
答案 2 :(得分:3)
posix_mem_offset()
似乎正是我所寻找的。
它不仅会告诉您是否映射了地址,而且如果它恰好被映射,它会隐式地为您提供它所属的映射区域的边界(通过在len
参数中提供SIZE_MAX )。
因此,在强制执行MAP_FIXED
之前,我可以使用posix_mem_offset()
来验证我正在使用的地址尚未映射。
我也可以使用msync()
或mincore()
(检查ENOMEM
错误告诉您地址已经映射了),但后来我会成为blinder(没有关于该区域的信息)地址映射的地方)。此外,msync()
具有可能对性能产生影响的副作用,mincore()
仅为BSD(不是POSIX)。