2017-02-13 67 views
0

尝试使用mmap在大文件中搜索模式。该文件是巨大的(比物理内存的方式更多)。我担心的是,如果我使用文件大小作为mmap()的第二个参数,将不会有足够的物理内存来满足系统调用。所以我用了0x1000作为长度,希望操作系统在我的指针移动时自动映射文件的右侧部分。但是下面的代码片段给出了分段错误。映射大文件并扫描数据

任何想法?

#include <stdio.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <unistd.h> 
#include <sys/mman.h> 

long fileSize(char *fname) { 
    struct stat stat_buf; 
    int rc = stat(fname, &stat_buf); 
    return rc == 0 ? stat_buf.st_size : -1; 
} 
int main(int argc, char *argv[]) { 
    long size = fileSize(argv[1]); 
    printf("size=%ld\n", size); 
    int fd = open(argv[1], O_RDONLY); 
    printf("fd=%d\n", fd); 
    char *p = mmap(0, 0x1000, PROT_READ, MAP_SHARED, fd, 0); 
    if (p == MAP_FAILED) { 
     perror ("mmap"); 
     return 1; 
    } 
    long i; 
    int pktLen; 
    int *pInt; 
    for (i=0; i < size; i+=4) { 
     pInt = (int*)(p+i); 
     if (pInt[i] == 0x12345678) { 
      printf("found it at %ld\n", i); break; 
     } 
    } 
    if (i == size) { 
     printf("didn't find it\n"); 
    } 
    close(fd); 
    return 0; 
} 

更新 原来我做了一个愚蠢的错误

线 if (pInt[i] == 0x12345678)应该已经if (pInt[0] == 0x12345678)

+3

“我的担心是,如果我使用文件大小作为mmap()的第二个参数,则不会有足够的物理内存”。映射文件不会立即保留这些物理内存。所以你的担心是没有根据的。虚拟内存另一方面可能是一个问题。 “希望操作系统在我的指针移动时自动映射文件的正确部分”。关于你希望做什么 - 为什么不阅读手册页并找出它的实际内容? – kaylum

+0

@kaylum,手册页只提及'length参数指定映射的长度',但它并没有解决我担心在执行此操作时可能会耗尽内存(RES或VM)。我真的不需要大量的内存,因为我只需要一次读一小段。 – packetie

+0

如果文件很大,请使用常规I/O而不是内存映射。你不会用完空间。在你不必担心连续块之间的模式匹配的情况下,这非常容易,只要块大小是4的倍数,这看起来就是这种情况。如果你只扫描一次文件,存储器映射并不是那么有用。如果您多次扫描,则更有意义 - 如果文件适合内存。考虑'memmem()'函数是否提供了帮助。 –

回答

1

SIGSEGV是因为你所访问超出0x1000字节(在for循环)。您必须对fd完整size字节的mmap()字节。

需求分页虚拟内存子系统的概念帮助像您一样完全相同的场景 - 应用程序/比物理内存大小的应用程序的数据更大。在mmap()之后,当你访问(虚拟)地址时,如果没有物理页面映射到它(页面错误),内核将找出可以使用的物理页面(页面替换)。

fd = open(argv[1], O_RDONLY); 

ptr = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0); 

/* Consume the entire file's data as needed */ 

munmap(ptr, file_size); 

或者你可以把mmap()/munmap()围绕循环扫描的文件中PAGE_SIZE或在PAGE_SIZE倍数。 mmap() - offset的最后一个参数将会派上用场。

从手册页:

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); 
int munmap(void *addr, size_t length); 

伪代码:

fd = open(argv[1], O_RDONLY); 

last_block_size = file_size % PAGE_SIZE; 
num_pages = file_size/PAGE_SIZE + (last_block_size ? 1 : 0) 

for (int i = 0; i < num_pages; i++) { 
    block_size = last_block_size && (i == num_pages - 1) ? last_block_size : PAGE_SIZE; 

    ptr = mmap(NULL, block_size, PROT_READ, MAP_PRIVATE, fd, i * PAGE_SIZE); 

    /* Consume the file's data range (ptr, ptr+block_size-1) as needed */ 

    munmap(ptr, block_size); 
} 

请使用MAP_PRIVATE作为映射可能只需要为你的过程中单独。它只是避免了内核对MAP_SHARED的额外步骤。编号:应该是MAP_PRIVATE代替MAP_ANON。改变。

+0

感谢@ReddyVeeru的详细回复。我尝试了'MAP_SHARED'和'MAP_PRIVATE',在这两种情况下,我的RES和VM内存使用率都非常高,最终程序死机。有任何想法吗? – packetie

+0

@packetie - 请仔细检查所有变量的数据类型是否有溢出情况。 你的数据文件的大小是多少? 由于'I + = 4'在用于'对(I = 0; I <大小; I + = 4)','size'有,如果它不是已被向下舍入为4个最近倍数。 – ReddyVeeru

+0

这是我的工作版本 - http://pastebin.com/K5mxPJs4 不幸的是,我可以测试最大文件大小为“4GB-1B(一个字节少于4GB)”与64位的Ubuntu x86-64位处理器。使用'dd if =/dev/urandom of = bigfile.txt count = 4194304 bs = 1024'命令生成的文件。 – ReddyVeeru

1

使用

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); 

在上面的代码中的要点:

  1. 你需要用例如启动代码

    #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的特定的功能。
     

  2. mappinglengthman 2 mmap)必须页大小(sysconf(_SC_PAGESIZE))的倍数。
     

  3. MAP_NORESERVE标志告诉内核映射仅由文件支持,因此允许大于可用RAM + SWAP。
     

  4. 因为映射本身包含引用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)advicePOSIX_MADV_DONTNEEDPOSIX_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'的测试运行之间清除页面缓存。)

+0

非常感谢@ nominal-animal的详细解释。你可以用这种方式处理1TB文件,真是太神奇了。我尝试了原始帖子中的示例代码(修复了这个愚蠢的bug),发现RES内存使用率跳至4.2GB。使用'posix_madvise(地图,映射,POSIX_MADV_SEQUENTIAL)'似乎并没有减少峰值内存消耗。不知道是否有办法限制偷看记忆。谢谢 – packetie

+0

@packetie:如果你知道你正在寻找的模式 - 无论是固定模式还是例如正则表达式 - ,可以构建一个有限状态机(其产生有限状态机,用于寻找指定的模式即写入代码),以寻找在并行模式,在这种情况下只需要在数据单次通过,并且不需要存储器映射。另一种选择是仅仅部分地映射数据,例如, 512 MiB +最大长度模式“窗口”,一次将窗口向上滑动512 MiB。这一切都取决于您的模式查找代码。 –