这是一个示例程序,example.c:
/* Not required on 64-bit architectures; recommended anyway. */
#define _FILE_OFFSET_BITS 64
/* Tell the compiler we do need POSIX.1-2001 features. */
#define _POSIX_C_SOURCE 200112L
/* Needed to get MAP_NORESERVE. */
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifndef FILE_NAME
#define FILE_NAME "data.map"
#endif
#ifndef FILE_SIZE
#define FILE_SIZE 3221225472UL
#endif
int main(void)
{
const size_t size = FILE_SIZE;
const char *const file = FILE_NAME;
size_t page;
unsigned char *data;
int descriptor;
int result;
/* First, obtain the normal page size. */
page = (size_t)sysconf(_SC_PAGESIZE);
if (page < 1) {
fprintf(stderr, "BUG: sysconf(_SC_PAGESIZE) returned an invalid value!\n");
return EXIT_FAILURE;
}
/* Verify the map size is a multiple of page size. */
if (size % page) {
fprintf(stderr, "Map size (%lu) is not a multiple of page size (%lu)!\n",
(unsigned long)size, (unsigned long)page);
return EXIT_FAILURE;
}
/* Create backing file. */
do {
descriptor = open(file, O_RDWR | O_CREAT | O_EXCL, 0600);
} while (descriptor == -1 && errno == EINTR);
if (descriptor == -1) {
fprintf(stderr, "Cannot create backing file '%s': %s.\n", file, strerror(errno));
return EXIT_FAILURE;
}
#ifdef FILE_ALLOCATE
/* Allocate disk space for backing file. */
do {
result = posix_fallocate(descriptor, (off_t)0, (off_t)size);
} while (result == -1 && errno == EINTR);
if (result == -1) {
fprintf(stderr, "Cannot resize and allocate %lu bytes for backing file '%s': %s.\n",
(unsigned long)size, file, strerror(errno));
unlink(file);
return EXIT_FAILURE;
}
#else
/* Backing file is sparse; disk space is not allocated. */
do {
result = ftruncate(descriptor, (off_t)size);
} while (result == -1 && errno == EINTR);
if (result == -1) {
fprintf(stderr, "Cannot resize backing file '%s' to %lu bytes: %s.\n",
file, (unsigned long)size, strerror(errno));
unlink(file);
return EXIT_FAILURE;
}
#endif
/* Map the file.
* If MAP_NORESERVE is not used, then the mapping size is limited
* to the amount of available RAM and swap combined in Linux.
* MAP_NORESERVE means that no swap is allocated for the mapping;
* the file itself acts as the backing store. That's why MAP_SHARED
* is also used. */
do {
data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE,
descriptor, (off_t)0);
} while ((void *)data == MAP_FAILED && errno == EINTR);
if ((void *)data == MAP_FAILED) {
fprintf(stderr, "Cannot map file '%s': %s.\n", file, strerror(errno));
unlink(file);
return EXIT_FAILURE;
}
/* Notify of success. */
fprintf(stdout, "Mapped %lu bytes of file '%s'.\n", (unsigned long)size, file);
fflush(stdout);
#if defined(FILE_FILL)
memset(data, ~0UL, size);
#elif defined(FILE_ZERO)
memset(data, 0, size);
#elif defined(FILE_MIDDLE)
data[size/2] = 1; /* One byte in the middle set to one. */
#else
/*
* Do something with the mapping, data[0] .. data[size-1]
*/
#endif
/* Unmap. */
do {
result = munmap(data, size);
} while (result == -1 && errno == EINTR);
if (result == -1)
fprintf(stderr, "munmap(): %s.\n", strerror(errno));
/* Close the backing file. */
result = close(descriptor);
if (result)
fprintf(stderr, "close(): %s.\n", strerror(errno));
#ifndef FILE_KEEP
/* Remove the backing file. */
result = unlink(file);
if (result)
fprintf(stderr, "unlink(): %s.\n", strerror(errno));
#endif
/* We keep the file. */
fprintf(stdout, "Done.\n");
fflush(stdout);
return EXIT_SUCCESS;
}
要编译和运行,请使用例如
gcc -W -Wall -O3 -DFILE_KEEP -DFILE_MIDDLE example.c -o example
./example
上面将创建一个3GB(10243)的稀疏文件data.map,并将其中的中间字节设置为1(\x01)。文件中的所有其他字节保持为零。然后就可以运行了
du -h data.map
查看这样一个稀疏文件在磁盘上实际占用了多少空间,以及
hexdump -C data.map
如果您希望验证文件内容是我声称的内容。
您可以使用一些编译时标志(宏)来更改示例程序的行为方式:
-
'-DFILE_NAME="filename"'
使用文件名filename 而不是data.map。请注意,整个值是在单引号内定义的,因此 shell 不会解析双引号。 (双引号是宏值的一部分。)
-
'-DFILE_SIZE=(1024*1024*1024)'
使用 10243 = 1073741824 字节映射而不是默认的 3221225472。如果表达式包含 shell 会尝试计算的特殊字符,最好将其全部括在单引号或双引号中。
-
-DFILE_ALLOCATE
为整个映射分配实际磁盘空间。默认情况下,使用稀疏文件代替。
-
-DFILE_FILL
用(unsigned char)(~0UL) 填充整个映射,通常为 255。
-
-DFILE_ZERO
将整个映射清零。
-
-DFILE_MIDDLE
将映射中的中间字节设置为1。其他所有字节不变。
-
-DFILE_KEEP
不要删除数据文件。这对于探索映射在磁盘上实际需要多少数据很有用;使用例如du -h data.map.
在 Linux 中使用内存映射文件时需要考虑三个主要限制:
-
文件大小限制
FAT (MS-DOS) 等旧文件系统不支持大文件或稀疏文件。如果数据集是稀疏的(包含大洞),稀疏文件很有用;在这种情况下,未设置的部分不会存储在磁盘上,而是简单地读取为零。
由于许多文件系统存在大于 231-1 字节(2147483647 字节)的读写问题,当前的 Linux 内核内部将每个单个操作限制为 231- 1 个字节。读取或写入调用不会失败,它只是返回一个短计数。我不知道有任何类似限制llseek() 系统调用的文件系统,但是由于C 库负责将lseek()/lseek64() 函数映射到正确的系统调用,因此C 库(而不是内核)很可能会限制功能. (对于 GNU C 库和嵌入式 GNU C 库,这种系统调用映射取决于编译时标志。例如,请参阅 man 7 feature_test_macros、man 2 lseek 和 man 3 lseek64。
最后,文件位置处理在大多数 Linux 内核中都不是原子的。 (补丁在上游,但我不确定哪些版本包含它们。)这意味着如果多个线程以修改文件位置的方式使用相同的描述符,则文件位置可能会完全乱码。
-
内存限制
默认情况下,文件支持的内存映射仍受可用内存和交换限制的约束。也就是说,默认mmap() 行为是假设在内存压力下,脏页被交换,而不是刷新到磁盘。您需要使用特定于 Linux 的 MAP_NORESERVE 标志来避免这些限制。
-
地址空间限制
在 32 位 Linux 系统上,用户空间进程可用的地址空间通常小于 4 GiB;它是一个内核编译时选项。
在 64 位 Linux 系统上,大型映射会消耗大量 RAM,即使映射内容本身还没有出错。通常,每个单页需要 8 个字节的元数据(“页表条目”) 在内存中,或者更多,取决于架构。使用 4096 字节的页面,这意味着 0.1953125% 的最小开销,并设置例如仅在页表结构中,TB 映射需要 2 GB 的 RAM!
Linux 中的许多 64 位系统支持大页面以避免这种开销。在大多数情况下,由于configuration and tweaking and limitations,大页面的使用有限。内核也可能对进程对大页面映射的操作有限制。一个健壮的应用程序需要彻底回退到正常的页面映射。
内核可能会对用户空间进程施加比资源可用性更严格的限制。运行 bash -c 'ulimit -a' 以查看当前施加的限制。 (详细信息可在man bash-builtins 的ulimit 部分中找到。)