【问题标题】:max size a single pread can obtain单个 pred 可以获得的最大尺寸
【发布时间】:2014-03-23 23:11:35
【问题描述】:

我正在使用 pread 一次获取大量数据。

但如果我尝试收集大量数据(例如 100mb)并将其保存到数组中,我会遇到段错误......

对 pread 可以读取的最大字节数有硬性限制吗?

#define          _FILE_OFFSET_BITS                            64 
#define          BLKGETSIZE64                                _IOR(0x12,114,size_t)
#define          _POSIX_C_SOURCE                             200809L

#include <stdio.h>
#include <inttypes.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

int readdata(int fp,uint64_t seekpoint, uint64_t seekwidth) {
    int16_t  buf[seekwidth];

    if (pread(fp,buf,seekwidth,seekpoint)==seekwidth) {
        printf("SUCCES READING AT: %"PRIu64"| WITH READ WIDTH: %"PRIu64"\n",seekpoint,seekwidth);
        return 1;
    } else {
        printf("ERROR READING AT: %"PRIu64"| WITH READ WIDTH: %"PRIu64"\n",seekpoint,seekwidth);
        return 2;
    }

}





int main() {

    uint64_t    readwith,
                offset;
    int         fp=open("/dev/sdc",O_RDWR);

    readwith=10000;   offset=0;
    readdata(fp,offset,readwith);
    readwith=100000;   offset=0;
    readdata(fp,offset,readwith);
    readwith=1000000;   offset=0;
    readdata(fp,offset,readwith);
    readwith=10000000;   offset=0;
    readdata(fp,offset,readwith);
    readwith=10000000;   offset=0;
    readdata(fp,offset,readwith);
    readwith=100000000;   offset=0;
    readdata(fp,offset,readwith);
    readwith=1000000000;   offset=0;
    readdata(fp,offset,readwith);
    readwith=10000000000;   offset=0;
    readdata(fp,offset,readwith);
    readwith=100000000000;   offset=0;
    readdata(fp,offset,readwith);
    readwith=1000000000000;   offset=0;
    readdata(fp,offset,readwith);
    readwith=10000000000000;   offset=0;
    readdata(fp,offset,readwith);
    readwith=100000000000000;   offset=0;
    readdata(fp,offset,readwith);
    readwith=1000000000000000;   offset=0;
    readdata(fp,offset,readwith);

    close(fp);

}

【问题讨论】:

  • 我会说你正在抓取比你想要的更多的东西,并试图读取比文件末尾更远的​​字节。这可以解释段错误。你能粘贴你的代码吗?
  • 你确定你调用了一个足够大的缓冲区来存储数据的 pread() 吗?
  • 您是在读入堆栈分配的缓冲区,还是使用malloc 分配的缓冲区?后者对于非常大的缓冲区要可靠得多。
  • 堆栈分配。稍后我将发布示例代码。截至目前,它太长了。我堆栈分配,因为如果我没有太多,我真的不想处理 malloc...
  • 是的,所以你试图将 10 兆字节读入 8 兆字节堆栈。

标签: c linux posix


【解决方案1】:

pread 可以读取的最大字节数没有硬性限制。但是,在一个连续的块中读取大量数据可能是个坏主意。我稍后会描述一些替代方案。

在您的特定情况下,问题很可能是您尝试堆栈分配缓冲区。堆栈的可用空间有限;如果你运行cat /proc/&lt;pid&gt;/limits,你可以看到特定进程的内容(或者只是cat /proc/self/limits 来检查你正在运行的shell)。就我而言,恰好是 8388608,或 8 MB。如果你尝试使用超过这个限制,你会得到一个段错误。

您可以使用setrlimit 或特定于Linux 的prlimit 来增加最大堆栈大小,但这通常不被认为是一件好事;您的堆栈是永久分配给每个线程的东西,因此增加大小会增加每个线程分配给它的地址空间。在 32 位系统上(相关性越来越低,但仍然有 32 位系统,或者 64 位系统上的 32 位应用程序),这个地址空间实际上是相当有限的,所以只有几个线程具有大量分配的堆栈空间可能会耗尽您的地址空间。最好采取另一种方法。

这样一种替代方法是使用malloc 动态分配缓冲区。然后,您将仅在需要时使用此空间,而不是一直使用整个堆栈。是的,您确实必须记住之后将数据发送给free,但是在您的编程中稍加考虑就不是那么难了。

另一种可能适用于此类大量数据的方法是使用mmap 将文件映射到您的地址空间,而不是尝试将整个内容读入缓冲区。这样做是分配一个地址空间区域,并且每当您访问该地址空间时,将从该文件中读取数据以填充您正在读取的页面。当您想要随机访问文件时,这可能非常方便,但实际上不会读取整个内容,而是会跳过文件。只会读取您实际访问的页面,而不是浪费时间将整个文件读入缓冲区然后只访问其中的一部分。

如果您使用mmap,则需要记住munmap 之后的地址空间,但如果您使用的是 64 位系统,那么记住munmap 就没有那么重要了如果您记得 free 分配的内存(在 32 位系统上,地址空间实际上非常宝贵,因此保留大型映射仍然会导致问题)。 mmap 只会占用地址空间,不会占用实际内存;由于它是由文件支持的,如果存在内存压力,内核可以将任何脏数据写入磁盘并停止将内容保存在内存中,而对于分配的缓冲区,它实际上需要将数据保存在 RAM 或交换空间中,这通常是相当有限的资源。如果你只是用它来读取数据,它甚至不必将脏数据刷到磁盘,它可以释放页面并重用它,如果你再次访问该页面,它会读取它回来了。

如果您不需要一次随机访问所有数据,最好在循环中以较小的块读取和处理数据。然后,您可以简单地使用堆栈分配,而不必担心增加分配给堆栈的地址空间量。

编辑添加:根据您的示例代码和其他问题,您似乎正在尝试将整个 2TB 磁盘作为单个阵列读取。在这种情况下,您肯定需要使用mmap,因为您可能没有足够的 RAM 来将全部内容保存在内存中。这是一个例子;请注意,此特定示例特定于 Linux:

#include <stdio.h>
#include <err.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <unistd.h>

int main(int argc, char **argv) {
    if (argc != 2)
        errx(1, "Wrong number of arguments");

    int fd = open(argv[1], O_RDONLY);
    if (fd < 0)
        err(2, "Failed to open %s", argv[1]);

    struct stat statbuf;
    if (fstat(fd, &statbuf) != 0)
        err(3, "Failed to stat %s", argv[1]);

    size_t size;
    if (S_ISREG(statbuf.st_mode)) {
        size = statbuf.st_size;
    } else if (S_ISBLK(statbuf.st_mode)) {
        if (ioctl(fd, BLKGETSIZE64, &size) != 0)
            err(4, "Failed to get size of block device %s", argv[1]);
    }

    printf("Size: %zd\n", size);

    char *mapping = mmap(0, size, PROT_READ, MAP_SHARED, fd, 0);
    if (MAP_FAILED == mapping)
        err(5, "Failed to map %s", argv[1]);

    /* do something with `mapping` */

    munmap(mapping, statbuf.st_size);

    return 0;
}

【讨论】:

  • mmap 如何处理块设备?因为它似乎没有缓冲,如果我读到比块大小小的东西,它会做什么?
  • @user3021085 块设备本身不是无缓冲的;事实上,“块设备”的意思是它的 I/O 是通过页面缓存缓冲的。当您对其进行任何形式的读取或写入时,都会通过页面缓存。当您映射它时,它以页面大小的增量映射;超出实际底层文件范围的任何内容都将设置为 0。因此,您可以读取或写入较小的增量,但所有实际 I/O 都将通过页面缓存,以页面大小的块。
  • 好的,谢谢。我会尝试使用 mmap 然后即使使用 malloc 它也会失败......我为它发布了一个不同的问题
  • @user3021085 啊,是的,如果您尝试将整个 2TB 磁盘作为单个连续阵列访问,mmap 是您的最佳选择。
猜你喜欢
  • 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
相关资源
最近更新 更多