代码之家  ›  专栏  ›  技术社区  ›  Matt Jordan

与机器相关的写入失败,错误代码为EINVAL

  •  3
  • Matt Jordan  · 技术社区  · 17 年前

    在实际问题之前,这有一些漫长的背景,然而,需要一些解释,以期消除一些转移注意力的地方。

    • 崩溃总是发生在同一组数据上;然而,它们不会出现在所有数据集上
    • 对于导致崩溃的一组数据,即使具有类似的特征,即操作系统、RAM量等,也无法在所有机器上再现崩溃。

     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)是一个空指针。然而,围绕此函数进行的一些简单检查验证了对其进行的任何调用都不是这种情况。

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

    更新: 从目前为止的帖子来看,还有其他几点:

    另一个更新:

    下面是一个失败的例子,上面的代码示例中有一些方便的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
    

    1.错误仅发生在硬盘写入时间较慢的计算机上。具有完全相同硬件规格但具有不同RAID配置(RAID 0与RAID 1)的两台PC将具有不同的结果。RAID 0将正确处理数据;RAID 1将失败。同样,硬盘速度较慢的旧电脑也会出现故障;具有更快硬盘驱动器但处理器/内存相似的新PC可以工作。 2.书写大小很重要。当我们将传递给_write的数据量限制为64 MB时,除了一个文件外,所有文件都成功了。当我们将其限制为32MB时,所有文件都成功了。我们使用的库的性能受到了影响——这是该库的一个限制,与_write或我们看到的问题无关——但这是我们唯一的“软件”修复。

    不幸的是,我从来没有得到一个好的答案(我们正准备就此致电微软,但我们不得不让业务部门批准技术支持电话的费用),为什么EINVAL会被退回。根据我们所能找到的,它并没有记录在C库API的任何地方。

    如果有人找到了一个好的答案,请把它贴在这里,我会把它标记为答案。我很想为这个传奇故事得出一个结论,即使它不再直接适用于我。

    5 回复  |  直到 17 年前
        1
  •  4
  •   Jonathan Leffler    17 年前

    我们遇到了一个非常相似的问题,我们很容易就能复制出来。我们首先编译了以下程序:

    #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
  •  1
  •   bk1e    17 年前

    你看了实施情况吗 _write() 在随Visual Studio安装的CRT(C运行时)源代码中( C:\Program Files\Microsoft Visual Studio 8\VC\crt\src\write.c )?

    至少有两种情况会导致 _write() 设置 errno 向 EINVAL :

    1. buffer 如您所述,为NULL。
    2. count 当文件以UTF-16格式(或UTF-8?注释与代码不匹配)以文本模式打开时,参数很奇怪。这是文本文件还是二进制文件?如果是文本,它有字节顺序标记吗?
    3. 也许还有另一个功能 _write() 错误号 向 EINVAL ?

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

        3
  •  1
  •   Himanshu THE ONLY ONE    13 年前

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

    - EBADF
    - ENOSPC
    - EINVAL.
    

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

        4
  •  0
  •   Ana Betts    17 年前

    你可能会不小心在其他地方误用指针,从而破坏自己的堆栈——如果你能找到一台复制机,试着在打开所有内存检查的情况下在Application Verifier下运行你的应用程序

        5
  •  0
  •   George Stocker NotMe    17 年前

    我想到了两个想法。。要么您正走过缓冲区的尽头,试图将数据写出来,要么缓冲区的分配失败。在调试模式下,问题不会像在发布模式下那样明显。

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

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

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

    由于这些函数中的大多数都封装了内核调用WriteFile()(或NtWrite File()),因此可能会出现内核内存不足以处理缓冲区写入的情况。但是,我不确定,因为我不知道代码什么时候从UM跳到KM。

    不知道这些是否会有所帮助,但希望如此。..

    如果你能提供更多细节,请提供。有时,仅仅告诉别人这个问题就会触发你的大脑“等一下!”,你会想出来的。嘿。。