使用
struct stat info;
long page;
const char *map;
size_t size, mapping;
int fd, result;
page = sysconf(_SC_PAGESIZE);
if (page < 1L) {
fprintf(stderr, "Invalid page size.\n");
exit(EXIT_FAILURE);
}
fd = open(filename, O_RDONLY);
if (fd == -1) {
fprintf(stderr, "%s: Cannot open file: %s.\n", filename, strerror(errno));
exit(EXIT_FAILURE);
}
result = fstat(fd, &info);
if (result == -1) {
fprintf(stderr, "%s: Cannot get file information: %s.\n", filename, strerror(errno));
close(fd);
exit(EXIT_FAILURE);
}
if (info.st_size <= 0) {
fprintf(stderr, "%s: No data.\n", filename);
close(fd);
exit(EXIT_FAILURE);
}
size = info.st_size;
if ((off_t)size != info.st_size) {
fprintf(stderr, "%s: File is too large to map.\n", filename);
close(fd);
exit(EXIT_FAILURE);
}
/* mapping is size rounded up to a multiple of page. */
if (size % (size_t)page)
mapping = size + page - (size % (size_t)page);
else
mapping = size;
map = mmap(NULL, mapping, PROT_READ, MAP_SHARED | MAP_NORESERVE, fd, 0);
if (map == MAP_FAILED) {
fprintf(stderr, "%s: Cannot map file: %s.\n", filename, strerror(errno));
close(fd);
exit(EXIT_FAILURE);
}
if (close(fd)) {
fprintf(stderr, "%s: Unexpected error closing file descriptor.\n", filename);
exit(EXIT_FAILURE);
}
/*
* Use map[0] to map[size-1], but remember that it is not a string,
* and that there is no trailing '\0' at map[size].
*
* Accessing map[size] to map[mapping-1] is not allowed, and may
* generate a SIGBUS signal (and kill the process).
*/
/* The mapping is automatically torn down when the process exits,
* but you can also unmap it with */
munmap(map, mapping);
在上面的代码中的要点:
你需要用例如启动代码
#define _POSIX_C_SOURCE 200809L
#define _BSD_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
的_BSD_SOURCE
需要被定义MAP_NORESERVE
,即使它是一个GNU/Linux的特定的功能。
mapping
(length
在man 2 mmap
)必须页大小(sysconf(_SC_PAGESIZE)
)的倍数。
MAP_NORESERVE
标志告诉内核映射仅由文件支持,因此允许大于可用RAM + SWAP。
因为映射本身包含引用in-kernel,所以您可以(但不需要)关闭引用映射文件的文件描述符而不会出现问题。
年前,在不同的论坛,我发现a simple program to manipulate a terabyte of data(1的TiB = 1,099,511,627,776字节)使用这个非常的方法(虽然它采用的是稀疏备份文件,即大多隐含零,小于250 MB的实际数据写入备份文件 - 主要是为了减少所需的磁盘空间量)。当然,它需要一台运行Linux的64位机器,因为32位机器上的虚拟内存限制为2 = 4 GiB(Linux不支持分段内存模型)。
Linux内核在选择将哪些页面保存在RAM中以及要驱逐哪些页面方面效率惊人。当然,通过使用posix_madvise(address, length, advice)
与advice
为POSIX_MADV_DONTNEED
或POSIX_MADV_WILLNEED
,告诉内核哪些部分映射不可能访问(因此可能会被驱逐),您可以更有效地实现这一点。这有一个好处,就是不像取消映射“不需要”的部分,如果需要的话,您可以重新访问该映射的那部分。 (如果页面已经被清除,映射的访问只会被阻塞,直到页面被重新加载到内存中。换句话说,您可以使用posix_madvise()
来“优化”驱逐逻辑,而不限制映射的哪一部分可以访问)。
在你的情况下,如果你使用例如线性或半线性搜索数据memmem()
,您可以使用posix_madvise(map, mapping, POSIX_MADV_SEQUENTIAL)
。
就我个人而言,我会先运行搜索,而不使用任何posix_madvise()
调用,然后使用相同的数据集(当然还有几次运行)查看它是否具有足够显着的积极差异。 (您可以放心 - 不会有丢失任何数据的风险 - 如果您希望在定时运行之间排除大文件(大部分)已被缓存的影响,请在使用sudo sh -c 'sync ; echo 3 > /proc/sys/vm/drop_caches ; sync'
的测试运行之间清除页面缓存。)
“我的担心是,如果我使用文件大小作为mmap()的第二个参数,则不会有足够的物理内存”。映射文件不会立即保留这些物理内存。所以你的担心是没有根据的。虚拟内存另一方面可能是一个问题。 “希望操作系统在我的指针移动时自动映射文件的正确部分”。关于你希望做什么 - 为什么不阅读手册页并找出它的实际内容? – kaylum
@kaylum,手册页只提及'length参数指定映射的长度',但它并没有解决我担心在执行此操作时可能会耗尽内存(RES或VM)。我真的不需要大量的内存,因为我只需要一次读一小段。 – packetie
如果文件很大,请使用常规I/O而不是内存映射。你不会用完空间。在你不必担心连续块之间的模式匹配的情况下,这非常容易,只要块大小是4的倍数,这看起来就是这种情况。如果你只扫描一次文件,存储器映射并不是那么有用。如果您多次扫描,则更有意义 - 如果文件适合内存。考虑'memmem()'函数是否提供了帮助。 –