映射大文件并扫描数据

时间:2017-02-13 04:46:45

标签: c linux

尝试使用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)

2 个答案:

答案 0 :(得分:2)

使用

instance MonadTrans (StateT s) where
    lift = ...
    returnT = ...
    bindT = ...

...

instance (MonadTrans t, Monad m) => Monad (t m) where
    return  = returnT
    a >>= b = a `bindT` b

上面代码中的重点:

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

    #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才能定义,即使它是GNU / Linux特有的功能。

  2. MAP_NORESERVEman 2 mmap中的mapping)必须是页面大小的倍数(length)。

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

  4. 您可以(但不需要)关闭引用映射文件的文件描述符而不会出现问题,因为映射本身包含内核中的引用。

  5. 多年前,在另一个论坛上,我使用这种方法显示a simple program to manipulate a terabyte of data(1 TiB = 1,099,511,627,776字节)(尽管它使用稀疏后备文件;即大多数是隐式零,实际小于250 MB写入备份文件的数据 - 主要是为了减少所需的磁盘空间量。当然,它需要运行Linux的64位机器,因为32位机器上的虚拟内存限制为2 32 = 4 GiB(Linux不支持分段内存模型)。

    Linux内核在选择保留在RAM中的页面以及要逐出的页面方面效率惊人。当然,通过使用posix_madvise(address, length, advice) MAP_NORESERVE advice告诉内核您不可能访问(因此可以驱逐)映射的哪些部分,您可以提高效率。 }或POSIX_MADV_DONTNEED。这样做的好处是,与取消映射“dontneed”部分不同,如果需要,您可以重新访问映射的那一部分。 (如果页面已被逐出,则对映射的访问将直到页面被重新加载到内存中为止。换句话说,您可以使用POSIX_MADV_WILLNEED来“优化”逐出逻辑,而不限制其中的哪一部分可以访问映射。)

    在您的情况下,如果您使用数据对数据进行线性或半线性搜索。 memmem(),您可以使用posix_madvise()

    就个人而言,我首先运行搜索而不使用任何posix_madvise(map, mapping, POSIX_MADV_SEQUENTIAL)调用,然后使用相同的数据集(当然还有几次运行)查看它是否产生了足够大的正面差异。 (您可以安全地 - 没有丢失任何数据的风险 - 使用posix_madvise()清除测试运行之间的页面缓存,如果您希望在时间运行之间排除大型文件(大部分)已经缓存的影响。)

答案 1 :(得分:1)

SIGSEGV是因为您在0x1000字节之外(for循环)访问了。您必须mmap() size的完整fd个字节。

虚拟内存子系统需求分页的概念有助于完全像您的情况一样 - 应用程序/应用程序数据大于物理内存大小。在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_SIZEPAGE_SIZE的倍数扫描文件。 mmap() - offset的最后一个arg会派上用场。

来自man-page:

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。改变。