【问题标题】:fread() on 6gb file fails6gb 文件上的 fread() 失败
【发布时间】:2010-07-20 00:20:19
【问题描述】:

好的,我一直在阅读 fread() [返回类型 size_t] 并看到了几篇关于大文件和其他一些问题的帖子 - 但我仍然遇到一些问题。该函数传入一个文件指针和一个 long long int。 lld 来自 main 我使用另一个函数来获取实际文件大小,即 6448619520 字节。

char *getBuffer(FILE *fptr, long long size) {
    char *bfr;
    size_t result;

    printf("size of file in allocate buffer:  %lld\n", size);
        //size here is 6448619520


    bfr = (char*) malloc(sizeof(char) * size);
    if (bfr == NULL) {
        printf("Error, malloc failed..\n");
        exit(EXIT_FAILURE);
    }
        //positions fptr to offset location which is 0 here.
    fseek(fptr, 0, SEEK_SET);
        //read the entire input file into bfr
    result = fread(bfr, sizeof(char), size, fptr);
    printf("result = %lld\n",  (long long) result);


    if(result != size)
    {
        printf("File failed to read\n");
        exit(5);
    }
    return (bfr);

}

我已经在 1-2gb 大小的文件上对其进行了测试,它工作正常,但是,当我在 6gb 文件上测试它时,没有任何内容被读入缓冲区。忽略其他结果,(重点关注结果的粗体),问题在于读取数据 bfr。这是我得到的一些结果。

文件的第一个是 735844352 字节 (700+MB)

root@redbox:/data/projects/C/stubs/# ./testrun -x 45004E00 -i /data/Helix2008R1.iso

图像文件是 /data/Helix2008R1.iso
十六进制字符串 = 45004E00
>文件总大小:735844352
获取缓冲区中的文件大小:735844352
结果 = 735844352

** 开始解析命令行十六进制值:45004E00
十六进制字符串的总字节数:4

十六进制字符串搜索结果:
在字节位置找到十六进制字符串 45004E00:37441
在字节位置找到十六进制字符串 45004E00:524768
....

针对 6gb 文件运行 #2: root@redbox:/data/projects/C/stubs/# ./testrun -x BF1B0650 -i /data/images/sixgbimage.img

图像文件是/data/images/sixgbimage.img
十六进制字符串 = BF1B0650
文件总大小:6448619520
分配缓冲区中的文件大小:6448619520
结果 = 0
文件读取失败

我仍然不确定为什么它在大文件而不是小文件上失败,这是一个 >4gb 的问题。我正在使用以下内容:

/* Support Large File Use */
#define _LARGEFILE_SOURCE 1
#define _LARGEFILE64_SOURCE 1
#define _FILE_OFFSET_BITS   64

顺便说一句,我使用的是 ubuntu 9.10 机器(2.6.x 内核)。 tia。

【问题讨论】:

  • 它是 32 位函数吗?使用 32 位,您只能访问 4 GB 的字节可寻址内存。如果您无法访问超过 4GB 的任何内容,我怀疑这至少是部分原因。
  • 是的,它是一个 32 位函数。我猜这可能是一个 >4gb 的问题,仍在研究如何解决这个问题..
  • 我读对了吗?您想一次将一个 6GB 的文件直接读入 RAM 吗?不要那样做,即使你认为你有足够的内存。像 sarnold 建议的那样使用 mmap。
  • 还有 32 位功能?这是否意味着您使用的是 32 位操作系统?在那种情况下你不能 fread() 6Gb,没有足够的内存地址空间。
  • 是的,在 32 位操作系统上 - 明白了,我现在知道这是不可能的 :) 必须一次读取小块,比如每 512 个字节或一个扇区。

标签: c


【解决方案1】:

如果您只是要阅读文件而不是修改它,我建议使用mmap(2) 而不是fread(3)。这应该更有效,尽管我没有在大文件上尝试过。如果这是您想要的,您需要更改我非常简单的找到/未找到来报告偏移量,但我不确定您想要指针做什么。 :)

#define _GNU_SOURCE
#include <string.h>

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


int main(int argc, char* argv[]) {
    char *base, *found;
    off_t len;
    struct stat sb;
    int ret;
    int fd;
    unsigned int needle = 0x45004E00;

    ret = stat(argv[1], &sb);
    if (ret) {
            perror("stat");
            return 1;
    }

    len = sb.st_size;

    fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
            perror("open");
            return 1;
    }

    base = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
    if (!base) {
            perror("mmap");
            return 1;
    }

    found = memmem(base, len, &needle, sizeof(unsigned int));
    if (found)
            printf("Found %X at %p\n", needle, found);
    else
            printf("Not found");
    return 0;
}

一些测试:

$ ./mmap ./mmap
Found 45004E00 at 0x7f8c4c13a6c0
$ ./mmap /etc/passwd
Not found

【讨论】:

  • 我以前一直用malloc,以前从来没有用过mmap...在这里看到了对这两个的一个很好的解释:stackoverflow.com/questions/1739296/malloc-vs-mmap-in-c
  • 在我看来,在这么大的缓冲区中使用fread 是一种滥用。即使您想在读取后更改缓冲区,您也应该使用mmap:在mmap 的标志中提供MAP_PRIVATE 确保您的副本的所有更改都保留在内存中并且不会重新写入文件。 mmap 效率更高,因为只要您不修改它们,它就不需要换出bfr 的任何页面。所有页面都直接在页面缓存中处理。如果您真的不想更改内容,请将其映射为只读,那么即使您的程序的多个实例也可以使用相同的物理页面共存。
【解决方案2】:

如果这是一个 32 位进程,如您所说,那么 size_t 是 32 位的,而您只是不能在进程的地址空间中存储超过 4GB(实际上,在实践中,有点小于 3GB)。在此行中:

bfr = (char*) malloc(sizeof(char) * size);

乘法的结果将以 SIZE_MAX + 1 为模减少,这意味着它只会尝试分配大约 2GB。同样,这一行中的size 参数也会发生同样的事情:

result = fread(bfr, sizeof(char), size, fptr);

如果您希望在 32 位进程中处理大文件,您必须一次只处理其中的一部分(例如,读取前 100 MB,处理那个,读取下一个 100 MB,...)。您无法一次读取整个文件 - 只是没有足够的内存可供您的进程执行此操作。

【讨论】:

  • 好点,这是我最好奇的。看起来我必须读取每个文件读取 x MB,直到达到文件大小的末尾。
【解决方案3】:

fread 失败时,它会设置errno 来指示失败的原因。调用返回零的freaderrno 的值是多少?

更新: 您是否需要一口气阅读整个文件?如果您一次读取文件,例如 512MB,会发生什么情况?

根据您上面的评论,您使用的是 32 位操作系统。在这种情况下,您将无法一次处理 6 GB(其中一个,size_t 将无法容纳这么大的数字)。但是,您应该能够以较小的块读取和处理文件。

我认为即使在 64 位操作系统上,将 6GB 文件读入内存也可能不是解决问题的最佳方法。您到底想要完成什么要求您缓冲 6GB 文件?可能有更好的方法来解决这个问题。

【讨论】:

  • fread() 返回 0 后,它返回的 errno 值为:22 结果:文件总大小:6448619520 获取缓冲区中文件大小:6448619520 结果 = 0 errno = 22. 文件读取失败
  • 一次读取一个扇区就可以了。只是我已经习惯了使用小测试文件,当我在更大的文件上测试它时,我没有考虑到我没有可用的内存......ty。
  • 您的问题是一个很好的问题,我应该很早就意识到,将这么大的内容读入内存是不可行且效率低下的。所以是的,一个小的重新设计是通过将读取分成小块,处理每个块直到我到达文件的末尾。该程序的总体原因是在 dd 映像或 VMFS 分区中获取十六进制偏移模式。一些 VMFS 分区往往会变得非常大,例如 200+GB 或更大。再说一次,这不是最好的方法,我现在明白了。
【解决方案4】:

在听取了大家的建议后,我将 6GB 文件分成了 4K 块,解析了十六进制字节,并且能够得到字节位置,这将有助于我以后从 VMFS 分区中提取 MBR dd图片。这是每个块读取它的快速而肮脏的方式:

#define DEFAULT_BLOCKSIZE 4096
...

while((bytes_read = fread(chunk, sizeof(unsigned char), sizeof(chunk), fptr)) > 0) {
    chunkptr = chunk;
    for(z = 0; z < bytes_read; z++) {
        if (*chunkptr == pattern_buffer[current_search]) {
            current_search++;
            if (current_search > (counter - 1)) {
                current_search = 0;
                printf("Hex string %s was found at starting byte location:  %lld\n",
                       hexstring, (long long int) (offsetctr-1));
                matches++;
            }
        } else {
            current_search = 0;
        }
        chunkptr++;
        //printf("[%lld]: %02X\n", offsetctr, chunk[z] & 0xff);
        offsetctr++;
    }
    master_counter += bytes_read;
}

...

这是我得到的结果...

root@redbox:~/workspace/bytelocator/Debug# ./bytelocator -x BF1B0650 -i /data/images/sixgbimage.img 

Total size of /data/images/sixgbimage.img file:  6448619520 bytes
Parsing the hex string now: BF1B0650

Hex string BF1B0650 was found at starting byte location:  18
Hex string BF1B0650 was found at starting byte location:  193885738
Hex string BF1B0650 was found at starting byte location:  194514442
Hex string BF1B0650 was found at starting byte location:  525033370
Hex string BF1B0650 was found at starting byte location:  1696715251
Hex string BF1B0650 was found at starting byte location:  1774337550
Hex string BF1B0650 was found at starting byte location:  2758859834
Hex string BF1B0650 was found at starting byte location:  3484416018
Hex string BF1B0650 was found at starting byte location:  3909721614
Hex string BF1B0650 was found at starting byte location:  3999533674
Hex string BF1B0650 was found at starting byte location:  4018701866
Hex string BF1B0650 was found at starting byte location:  4077977098
Hex string BF1B0650 was found at starting byte location:  4098838010


Quick stats:
================
Number of bytes that have been read:  6448619520
Number of signature matches found:  13
Total number of bytes in hex string:  4

【讨论】:

  • 我认为您的新程序会丢失跨越两个块的字节串副本:例如,块 0 中的 BF1B,块 1 中的 0650
  • 是的,你是对的。这是目前的限制之一,十六进制块之间的十六进制模式搜索仅搜索那些 4k 块内的模式,而不是跨越两个连续块的模式 - 这是必须解决的问题!
【解决方案5】:

您是否验证过mallocfread 实际上采用了正确类型的参数?您可能希望使用 -Wall 选项进行编译并检查您的 64 位值是否实际被截断。在这种情况下,malloc 不会报告错误,但最终会分配远远少于您所要求的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多