与EINVAL错误代码相关的机器相关_write失败

这在实际问题之前有一些冗长的背景,然而,它有一些解释,希望清除一些红色的鲱鱼。

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

  • 崩溃发生在各种硬件设置和操作系统上(尽管我们的客户仅限于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)是一个空指针。 然而,围绕此function的一些简单检查证实,在对它进行的任何调用中都不是这种情况。

但是,我们使用一些非常大的数据集来调用此方法 – 在一次调用中超过250MB,具体取决于输入数据。 当我们对这种方法的数据量施加了人为限制时,我们似乎已经解决了这个问题。 然而,这对于依赖于机器/依赖于/取决于月相的问题的代码修复而言。 所以现在的问题是:

  1. 是否有人知道_write可以在一次通话中处理的数据量有限制? 或者 – 禁止_write – Visual C ++支持的任何文件I / O命令?
  2. 由于这不会发生在所有机器上 – 或者甚至在每个足够大小的呼叫上(一个250 MB的呼叫都可以工作,另一个呼叫不会) – 是否有人知道用户,机器,组策略设置或文件夹权限会影响到这个吗?

更新:从目前为止的post中的其他几点:

  • 我们确实处理大缓冲区分配失败的情况。 由于第三方应用程序中读取我们正在创建的文件的性能原因,我们希望在一个大块中写出所有数据(尽管给出了此错误,但可能无法实现)
  • 我们在上面的例程中检查了size的初始值,它与分配的缓冲区的大小相同。 此外,当引发EINVAL错误代码时,size等于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.错误仅发生在硬盘上写入时间较慢的计算机上。 两台PC具有完全相同的硬件规格,但具有不同的RAID配置(RAID 0与RAID 1)会产生不同的结果。 RAID 0将正确处理数据; RAID 1会失败。 同样,硬盘速度较慢的旧PC也会失败; 具有更快硬盘驱动器的新型PC – 但类似的处理器/内存 – 可行。 2.写入大小很重要。 当我们将传递给_write的数据量限制为64 MB时,除了一个文件之外的所有文件都成功。 当我们将其限制为32 MB时,所有文件都成功了。 我们在使用的库中取得了性能影响 – 这是该库的限制,独立于_write或我们看到的问题 – 但它是我们唯一的“软件”修复。

不幸的是,我从来没有得到一个好的答案(我们就此打算致电微软,但我们不得不通过技术支持电话的费用来签署),以了解为什么EINVAL首先被退回。 它不是 – 从我们能够找到的 – 记录在C库API的任何地方。

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

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

 #include  #include  #include  #include  #include  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上,此问题显然已得到纠正。

我希望这有帮助! 西蒙

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

至少有两个条件导致_write()errnoEINVAL

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

如果可以可靠地重现此问题,则应该能够通过在设置错误代码的CRT源部分中放置断点来缩小错误源。 看来CRT的调试版本能够在发生错误时断言,但可能需要调整一些选项 (我还没有尝试过)。

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

 - EBADF - ENOSPC - EINVAL. 

窗户上没有EINTR。 随机系统中断导致此错误并且while (-1==nbytes && EINTR==errno);未被测试捕获while (-1==nbytes && EINTR==errno);

你可能会在其他地方意外滥用指针而丢弃你自己的堆栈 – 如果你能找到一个repro机器,试着在Application Verifier下运行你的应用程序并打开所有内存检查

想到两个想法..要么你走过缓冲区的末尾,又试图写出那些数据,或者缓冲区的分配失败了。 在调试模式下,问题不会像在发布模式下那样可见。

无论如何分配250兆内存可能是一个坏主意。 您最好分配一个固定大小的缓冲区,并以块的forms进行编写。

您是否在查找可能在写入操作之间保留文件的病毒扫描程序,从而使写入失败?

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

由于大多数这些函数都包装内核调用WriteFile(),(或NtWriteFile()),因此可能存在没有足够的内核内存来处理要写入的缓冲区的条件。 但是,我不确定,因为我不知道什么时候代码完全从UM跳到KM。

不知道是否有任何帮助,但希望它能…

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