使用mmap()搜索大文件(~1TB)

4
我正在开发一个项目,试图在文件系统(例如ext2)中搜索特定字节(例如0xAB)。我能够使用malloc()realloc()memchr()找到所需内容,但速度较慢,因此我正在考虑使用mmap()。我的目标是找到特定字节,然后将它们复制到一个结构体中,所以我有两个问题:(1)使用mmap()是否是最佳策略?(2)为什么下面的代码不起作用(我收到了EINVAL错误)?
更新:以下程序可以编译和运行,但我仍有几个问题:
1)对于大文件,它无法正确显示文件大小(对于1GB闪存驱动器显示正确大小,但对于32GB不是)*。
2)它没有正确地搜索映射**。
*使用stat64()获取正确大小的方法是否可行?如果是,请问我该如何将其添加到Makefile中?我没有经验处理Makefile,不知道该如何添加。
**这是搜索的正确方式吗?
#define _LARGEFILE64_SOURCE

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h> 
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)

int main(int argc, char **argv) {

    int fd = open("/dev/sdb1", O_RDONLY); 

    if(fd < 0) {
        printf("Error %s\n", strerror(errno));
        return -1;
    }

    const char * map;   

    off64_t size;
    size = lseek64(fd, 0, SEEK_END);
    printf("file size: %llu\n", size);
    lseek64(fd, 0, SEEK_SET);    

    map = mmap(0, size, PROT_READ, MAP_SHARED, fd, 0); 
    if (map == MAP_FAILED) { handle_error("mmap error"); }

    printf("Searching for magic numbers...\n");
    for (i=0; i < size; i++) {
    if(map[i] == 0X53 && map[i + 1] == 0XEF) {  
        if ((map[i-32] == 0X00 && map[i-31] == 0X00)  ||            
            (map[i-32] == 0X01 && map[i-31] == 0X00)  ||
            (map[i-32] == 0X02 && map[i-31] == 0X00)) {
            if(j <= 5) { 
                printf("superblock %d found\n", j);
                ++j; 
            } else break;

    int q;
    for(q=0; q<j; q++) {
        printf("SUPERBLOCK[%d]: %d\n", q+1, sb_pos[q]);
    }

    fclose(fd);
    munmap(map, size);
    return 0;
}

感谢您的帮助。

你应该检查 errno 变量以了解为什么 mmap 失败了。 - Giuseppe Pes
你读过这个问题吗? - Shark
它可能失败是因为找不到您请求的连续内存条(size)的长度。 - Shark
@Shark 如果程序找不到连续的内存,那么是否最好使用 malloc()realloc() 分配 X 字节,直到整个文件系统被搜索? - user2341909
听起来是一个有效的解决方法 :) 但这也取决于您可用的空闲内存量,因为malloc()realloc()可能会因与mmap()相同的原因而失败。只需确保您对齐得很好即可。 - Shark
显示剩余2条评论
3个回答

1

mmap是一种非常高效的处理大文件搜索的方法,特别是在存在内部结构的情况下(例如,在具有排序的固定大小记录的大文件上使用mmap将允许您进行二进制搜索,并且只会触及读取记录对应的页面)。

在您的情况下,您需要编译64位并启用大文件支持(并使用open(2))。

如果您的/dev/sdb1是一个设备而不是一个文件,则我认为stat(2)将不会显示实际大小。在我的机器上,这些设备的stat返回大小为0。我认为您需要以另一种方式获取大小。

关于地址空间:x86-64使用2^48字节的虚拟地址空间,即256 TiB。您不能使用所有的地址空间,但在大多数进程中很容易获得约127 TiB的连续地址空间。


是的,/dev/sdb1将是一个设备,程序正在运行在一台64位的Ubuntu机器上。如果我在一个32GB的USB驱动器上使用以下命令,我会得到一个1493172224: int fd = open("/dev/sdb1", O_RDONLY | O_LARGEFILE); ... off_t size; size = lseek64(fd, 0, SEEK_END); rewind(fd); - user2341909

0
我刚刚注意到我正在使用fopen(),我应该使用open()吗?
是的,你应该使用open()而不是fopen()。这也是为什么你会得到EINVAL错误的原因。
fopen("/dev/sdb1", O_RDONLY);
这段代码完全不正确。O_RDONLY是一个标志,应该与open()系统调用一起使用,而不是与fopen() libc函数一起使用。
你还应该注意,只有在运行具有大虚拟地址空间的平台上才能映射大文件。很明显:你应该有足够的虚拟内存来寻址你的文件。就英特尔而言,它应该只是x86_64,而不是x86_32。
我还没有尝试过使用真正大的文件(>4G)来做这件事。可能需要传递一些额外的标志到open()系统调用中。

0
我正在开发一个项目,试图在文件系统(例如ext2)中搜索特定的字节(例如0xAB)。 在您的情况下,将大文件mmap()到内存中是完全错误的方法。您只需要逐步处理具有固定大小的块的文件,大约为1MB。您可以使用mmap()或仅将其read()到内部缓冲区中-这并不重要。但是,如果您只想按顺序处理它,则将整个文件放入内存完全是杀鸡焉用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接