【问题标题】:Machine dependent _write failures with EINVAL error code机器相关的 _write 失败并带有 EINVAL 错误代码
【发布时间】:2009-02-25 00:53:44
【问题描述】:

这在实际问题之前有一些冗长的背景,但是,它有一些解释,希望能消除一些红鲱鱼。

我们的应用程序是用 Microsoft Visual C++ (2005) 开发的,它使用第 3 方库(我们幸运地拥有它的源代码)来导出另一个第 3 方应用程序中使用的压缩文件。该库负责创建导出的文件、管理数据和压缩,并通常处理所有错误。最近,我们开始收到反馈,在某些机器上,我们的应用程序在写入文件时会崩溃。根据一些初步探索,我们能够确定以下几点:

  • 崩溃发生在各种硬件设置和操作系统上(尽管我们的客户仅限于 XP / 2000)
  • 崩溃总是发生在同一组数据上;但是它们不会出现在所有数据集上
  • 对于导致崩溃的一组数据,崩溃无法在所有机器上重现,即使具有相似的特征,即操作系统、RAM 量等。
  • 只有在应用程序在安装目录中运行时才会出现该错误 - 不会在从 Visual Studio 构建、在调试模式下运行或什至在用户有权访问的其他目录中运行时
  • 无论文件是在本地驱动器还是映射驱动器上构建,都会出现问题

在调查问题后,我们发现问题出在以下代码块中(稍作修改以删除一些宏):

 while (size>0) {
    do {
        nbytes = _write(file->fd, buf, size);
    } while (-1==nbytes && EINTR==errno);
    if (-1==nbytes) /* error */
        throw("file write failed")
    assert(nbytes>0);
    assert((size_t)nbytes<=size);
    size -= (size_t)nbytes;
    addr += (haddr_t)nbytes;
    buf = (const char*)buf + nbytes;
}

具体来说,_write 返回错误代码 22 或 EINVAL。根据MSDN, _write 返回 EINVAL 意味着缓冲区(在本例中为 buf)是一个空指针。然而,围绕这个函数的一些简单检查验证了对它的任何调用都不是这种情况。

但是,我们确实会使用一些非常大的数据集调用此方法 - 一次调用超过 250MB,具体取决于输入数据。当我们对进入此方法的数据量施加人为限制时,我们似乎已经解决了这个问题。但是,这有点像代码修复了与机器相关/权限相关/取决于月相的问题。那么现在的问题是:

  1. 有人知道 _write 在一次调用中可以处理的数据量有限制吗?或者 - 除了 _write - Visual C++ 支持的任何文件 I/O 命令?
  2. 由于并非在所有计算机上都会发生这种情况 - 甚至在每个足够大小的调用上(一个 250 MB 的调用都可以工作,另一个调用不会) - 是否有人知道用户、计算机、组策略设置或会影响此的文件夹权限?

更新: 到目前为止的帖子中的其他几点:

  • 我们会处理大型缓冲区分配失败的情况。出于读取我们正在创建的文件的第 3 方应用程序的性能原因,我们希望将所有数据写入一个大块中(尽管鉴于此错误,这可能是不可能的)
  • 我们在上面的例程中检查了大小的初始值,它与分配的缓冲区大小相同。此外,当引发 EINVAL 错误代码时,大小等于 0,并且 buf 不是空指针 - 这让我认为这不是问题的原因。

另一个更新:

下面是一个失败示例,上面的代码示例中有一些方便的 printfs。

     while (size>0) {
    if (NULL == buf)
    {
        printf("Buffer is null\n");
    }
    do {
        nbytes = _write(file->fd, buf, size);
    } while (-1==nbytes && EINTR==errno);
    if (-1==nbytes) /* error */
    {
        if (NULL == buf)
        {
            printf("Buffer is null post write\n");
        }
        printf("Error number: %d\n", errno);
        printf("Buffer address: %d\n", &buf);
        printf("Size: %d\n", size);
        throw("file write failed")
    }
    assert(nbytes>0);
    assert((size_t)nbytes<=size);
    size -= (size_t)nbytes;
    addr += (haddr_t)nbytes;
    buf = (const char*)buf + nbytes;
}

如果失败,会打印出来:

Error number: 22
Buffer address: 1194824
Size: 89702400

请注意,没有成功写入任何字节并且缓冲区具有有效地址(并且没有触发 NULL 指针检查,在 _write 之前或之后)

最后更新

不幸的是,我们被事件所克服,无法最终解决这个问题。我们能够找到一些有趣(甚至可能令人不安)的事实。 1. 错误只发生在硬盘写入时间较慢的机器上。具有完全相同硬件规格但 RAID 配置不同(RAID 0 与 RAID 1)的两台 PC 会产生不同的结果。 RAID 0 会正确处理数据; RAID 1 会失败。同样,硬盘速度较慢的旧 PC 也会出现故障;具有更快硬盘驱动器但类似处理器/内存的较新PC可以工作。 2. 写入大小很重要。当我们将传递给 _write 的数据量限制为 64 MB 时,除了一个文件之外的所有文件都成功了。当我们将其限制为 32 MB 时,所有文件都成功了。我们在使用的库中遇到了性能问题 - 这是该库的限制,与 _write 或我们看到的问题无关 - 但这是我们唯一的“软件”修复。

不幸的是,我从来没有得到一个很好的答案(我们正要就此致电 Microsoft,但我们不得不让业务部门签署以支付技术支持电话的费用)关于 EINVAL 为何在第一名。它没有 - 根据我们能够找到的 - 记录在 C 库 API 的任何地方。

如果有人确实找到了一个好的答案,请在此处发布,我会将其标记为答案。我很想为这个传奇得出一个结论,即使它不再直接适用于我。

【问题讨论】:

    标签: c++ c debugging visual-c++


    【解决方案1】:

    我们有一个非常相似的问题,我们很容易重现。我们首先编译了以下程序:

    #include <stdlib.h>
    #include <stdio.h>
    #include <io.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    
    int main(int argc, char *argv[])
    { int len = 70000000;
      int handle= creat(argv[1], S_IWRITE | S_IREAD);
      setmode (handle, _O_BINARY);
      void *buf = malloc(len);
      int byteswritten = write(handle, buf, len);
      if (byteswritten == len)
        printf("Write successful.\n");
      else
        printf("Write failed.\n");
      close(handle);
      return 0;
    }
    

    现在,假设您在计算机 mycomputer 上工作,并且 C:\inbox 映射到共享文件夹 \\mycomputer\inbox。然后观察如下效果:

    C:\>a.exe C:\inbox\x
    Write successful.
    
    C:\>a.exe \\mycomputer\inbox\x
    Write failed.
    

    注意如果len改成60000000就没有问题了...

    基于此网页support.microsoft.com/kb/899149,我们认为这是“操作系统的限制”(使用 fwrite 观察到了相同的效果)。我们的解决方法是在失败时尝试将写入减少为 63 MB。此问题显然已在 Windows Vista 上得到纠正。

    我希望这会有所帮助! 西蒙

    【讨论】:

    • 如果该计划没有受到其他与经济相关的因素的影响,肯定会 - 或者至少会这样做。肯定出现了同样的问题。谢谢!
    【解决方案2】:

    您是否查看了随 Visual Studio (C:\Program Files\Microsoft Visual Studio 8\VC\crt\src\write.c) 一起安装的 CRT(C 运行时)源代码中 _write() 的实现?

    至少有两个条件导致_write()errno 设置为EINVAL

    1. 正如您所提到的,buffer 为 NULL。
    2. 当文件以 UTF-16 格式(或 UTF-8?cmets 与代码不匹配)以文本模式打开时,count 参数为奇数。这是文本文件还是二进制文件?如果是文本,是否有字节序标记?
    3. 也许_write() 调用的另一个函数也将errno 设置为EINVAL

    如果您可以可靠地重现此问题,您应该能够通过在设置错误代码的 CRT 源的部分中放置断点来缩小错误来源。似乎 CRT 的调试版本能够在错误发生时断言,但它可能需要tweaking some options(我没有尝试过)。

    【讨论】:

    • 它是一个二进制文件。我会看看 write.c 看看有没有什么明显的。
    • 唉——什么也没跳出来。由于它是二进制文件,因此不应应用 UTF-16 错误。我唯一能想到的是 WriteFile 本身正在设置 EINVAL 标志 - 这不是我们能够看到的。
    【解决方案3】:

    根据http://msdn.microsoft.com/en-us/library/1570wh78(v=VS.90).aspx errno 可以取值:

    - EBADF
    - ENOSPC
    - EINVAL.
    

    Windows 上没有 EINTR。随机系统中断导致此错误,测试未捕获while (-1==nbytes &amp;&amp; EINTR==errno);

    【讨论】:

      【解决方案4】:

      您可能会因不小心在其他地方误用指针而破坏自己的堆栈 - 如果您能找到一台重现机器,请尝试在应用程序验证程序下运行您的应用程序并打开所有内存检查

      【讨论】:

        【解决方案5】:

        我想到了两个想法。要么你走过缓冲区的末端并试图写出该数据,要么缓冲区的分配失败。在调试模式下不会像在发布模式下那样明显的问题。

        无论如何分配 250 兆内存可能是个坏主意。你最好分配一个固定大小的缓冲区,然后分块写。

        您是否寻找过诸如病毒扫描程序之类的东西,它们可能会在您的写入操作之间保留文件,从而导致写入失败?

        我知道在一次调用中可以传递写入的数据量没有限制,除非(就像我说的那样),您正在写入不属于您的数据(作为缓冲区的一部分)......

        由于这些函数中的大多数都包装了内核调用 WriteFile(),(或 NtWriteFile()),因此可能存在没有足够的内核内存来处理要写入的缓冲区的情况。但是,我不确定,因为我不知道代码究竟何时会从 UM 跳转到 KM。

        不知道这是否会有所帮助,但希望它会...

        如果您可以提供更多详细信息,请提供。有时只是告诉某人这个问题会触发你的大脑去“等一下!”,你会弄明白的。呵呵..

        【讨论】:

        • 就分配内存而言,我们首先做了大量工作来关闭 malloc,并且我们确实处理了 malloc 失败的情况(这远远早于这一点在代码中)。所以缓冲区应该分配得很好。
        • 您打开文件的调用是什么样的?只是好奇这是否会提供任何线索。你有没有第一次写总是失败,但第二次写的情况?如果您将代码设置为循环直到写入成功,它会起作用吗?不是修复,而是测试?
        • 不幸的是 - 我必须强调这个错误发生在我们必须使用的第 3 方库中 - 他们不使用简单的标准 FILE* 对象等。他们实际上已经创建了自己的 - 是的,这意味着所有的赌注都在发生的事情上。
        • 也许不是,但他们不得不使用最终调用 CreateFile()...那些包装 CreateFile... 而 EINVAL 只是意味着在某处传递了一个无效的参数。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-03-21
        • 1970-01-01
        • 2020-09-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多