【问题标题】:Seg fault with open command when trying to open very large file尝试打开非常大的文件时,打开命令出现 Seg 错误
【发布时间】:2010-01-15 03:29:08
【问题描述】:

我正在学校参加网络课程,并且是第一次使用 C/GDB。我们的任务是创建一个与客户端浏览器通信的网络服务器。我正在进行中,可以打开文件并将它们发送给客户。一切都很好,直到我打开一个非常大的文件然后我出现了故障。我不是 C/GDB 的专业人士,所以如果这导致我提出愚蠢的问题并且自己无法看到解决方案,我很抱歉,但是当我查看转储的核心时,我看到我的 seg 错误出现在这里:

if (-1 == (openfd = open(path, O_RDONLY)))

具体来说,我们的任务是打开文件并将其发送到客户端浏览器。我的算法是:

  1. 打开/错误捕获
  2. 将文件读入缓冲区/错误捕获
  3. 发送文件

我们还负责确保在发送非常大的文件时服务器不会崩溃。但我的问题似乎在于打开它们。我可以发送我所有的小文件就好了。有问题的文件为 29.5MB。

整个算法是:

ssize_t send_file(int conn, char *path, int len, int blksize, char *mime) {
  int openfd; // File descriptor for file we open at path
  int temp; // Counter for the size of the file that we send
  char buffer[len]; // Buffer to read the file we are opening that is len big

  // Open the file
  if (-1 == (openfd = open(path, O_RDONLY))) {
    send_head(conn, "", 400, strlen(ERROR_400));
    (void) send(conn, ERROR_400, strlen(ERROR_400), 0);
    logwrite(stdout, CANT_OPEN);
    return -1;
  }

  // Read from file
  if (-1 == read(openfd, buffer, len)) {
    send_head(conn, "", 400, strlen(ERROR_400));
    (void) send(conn, ERROR_400, strlen(ERROR_400), 0);
    logwrite(stdout, CANT_OPEN);
    return -1;
  }
  (void) close(openfd);

  // Send the buffer now
  logwrite(stdout, SUC_REQ);
  send_head(conn, mime, 200, len);      
  send(conn, &buffer[0], len, 0);
  return len;
}

我不知道我是 Unix/C 新手是否只是一个事实。对不起,如果是的话。 =(但非常感谢您的帮助。

【问题讨论】:

  • 围绕它显示一些代码。更好的是,编写一个显示该问题的最小程序。
  • 只要path 是指向可读内存中以 NUL 结尾的字符串的第一个字符的指针,我就无法想象该行有段错误。 (在许多情况下,open 更有可能返回 -1 并设置 errno=EFAULT。)您确定那是故障线吗?
  • 可能不是。我还不知道如何使用 GDB 或 Unix 环境来解决这个问题。但是当我在 gdb webserver webserver.core 中输入它时,它显示 Program Terminated with signal 11, Segmentation fault。它是 if (-1 == (openfd = open(path, O_RDONLY)) 行。我想这就是原因
  • 哦,亲爱的。 char buffer[len] -- 请不要在堆栈上分配巨大的数组(或巨大的任何东西)!

标签: c linux unix networking gdb


【解决方案1】:

我可能只是误解了您在问题中的意思,但我觉得我应该指出,一般来说,尝试一次读取整个文件是一个坏主意,以防您处理的只是太大了,您的内存无法处理。

分配一个特定大小的缓冲区更聪明,比如 8192 字节(好吧,这就是我经常做的事情),并且总是尽可能多地读取和发送,直到你读取( ) 操作为流结束返回 0(并且没有设置 errno)。

【讨论】:

  • 不错,但我倾向于使用10240。
  • 很公平。我想我只是喜欢 2 的幂。
  • @Richard:为什么是 10240?这不是 2 的幂。
  • @Platinum Azure:你并不孤单。我认识的许多程序员也发现 2 的幂更合乎逻辑。
  • 它不需要是 2 的幂,但它应该是 8192 的倍数,因为大多数内存页面的大小都是 4096 或 8192 字节,一些 PPC64 系统使用页面大小为 65536。
【解决方案2】:

我怀疑你有一个 stackoverflow(我应该在这个网站上使用这个词获得奖励积分)。

问题是您一次为堆栈上的整个文件分配缓冲区。对于较大的文件,此缓冲区比堆栈大,下次您尝试调用函数(并因此将一些参数放在堆栈上)时,程序会崩溃。

崩溃出现在打开的行,因为在堆栈上分配缓冲区实际上并没有写入任何内存,它只是更改了堆栈指针。当您调用 open 尝试将参数写入堆栈时,堆栈顶部现在溢出,这会导致崩溃。

解决方案就像 Platinum Azure 或 dreamlax 建议的那样,一次读取文件一点点,或者在堆上分配你的缓冲区将 malloc 或 new。

【讨论】:

  • 除非您使用 C99 的 VLA 或 alloca,否则分配可变数量的堆栈空间实际上并不容易。我喜欢这个假设……哦。我看到 OP 已经编辑了他们的问题以包含代码,并且他们正在使用 VLA。糟糕的OP!并为你 +1。
【解决方案3】:

与其使用可变长度数组,不如尝试使用malloc分配内存。

char *buffer = malloc (len);

...

free (buffer);

我刚刚在我的系统上做了一些简单的测试,当我使用大尺寸的可变长度数组(比如你遇到问题的尺寸)时,我也得到了一个 SEGFAULT。

【讨论】:

  • 我应该何时决定分配空间还是使用可变长度数组?如果我确实使用可变长度数组,我能做到的最大是什么?也许那含糊不清,但这似乎是我的问题的一部分。
  • 变长数组通常是通过在栈上腾出空间来实现的,与从malloc获得的内存相比,程序的栈通常很小。您可以制作的最大 VLA 取决于操作系统为您的进程提供了多少堆栈空间。您通常应该将可变长度数组保持在很小的范围内,我通常会达到 4 KB,即使这样我也会担心我要求的太多了,尽管有些系统有相当大的堆栈,大约 4 MB 甚至有时甚至 8 MB,但它依赖于系统。
  • 就个人而言,我讨厌使用在堆栈上分配的可变长度数组。我的意思是,它依赖于系统的事实意味着它可能不是确定性的,至少在不同的系统中是这样。使用堆和malloc,我可以更有信心获得我想要的数量,或者如果当时系统不足,我会耗尽内存。顺便说一句,无论如何你只能在堆中获得一定数量的内存,所以如果你正在处理一个非常大的文件(在 32 位系统上 > 4GB),如果你尝试这种方法,计算机会吐在你的脸上,因此我的缓冲溶液。
【解决方案4】:

你在堆栈上分配缓冲区,它太大了。

当您在堆栈上分配存储空间时,编译器所做的只是将堆栈指针减小到足以腾出那么多空间(这使堆栈变量分配保持在恒定时间)。它不会尝试触及任何堆叠的内存。然后,当你调用open()时,它会尝试将参数放入堆栈,发现堆栈溢出并死亡。

您需要对文件进行分块操作、内存映射 (mmap()) 或 malloc() 存储。 此外,path 应声明为 const char*

【讨论】:

  • 滑动mmap 窗口需要一些努力,但可能非常棒 :) (为什么不映射整个文件?嗯,有时你只有 2GB 的虚拟地址空间,你想工作一个 8GB 的​​文件...)
  • 是的,但是 Chris 说 23.5 MB,所以我建议了最小复杂度的解决方案。
猜你喜欢
  • 2020-11-17
  • 1970-01-01
  • 1970-01-01
  • 2011-10-06
  • 2017-10-02
  • 2013-02-06
  • 1970-01-01
  • 1970-01-01
  • 2014-03-11
相关资源
最近更新 更多