POSIX partial write()

7
根据SUSv4或POSIX.1-2008中所述http://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html#tag_16_685_08,如果往非阻塞管道/命名管道写入内容,则write()调用返回值可能小于nbytes。因此,需要检查返回值并在下面的循环中write()剩余的缓冲区内容。
while (bytes_to_write > 0) {
    select(...);                 // Or poll()
    retv = write(...);
    if (retv < 0)
        ...                      // Error
    bytes_to_write -= retv;
}

标准对于常规文件、特殊文件(也称为设备)和套接字,特别是基于流的套接字(例如TCP套接字和UNIX域套接字)没有任何说明。
那么,我有以下两个问题:
  • 在常规文件(或未设置O_NONBLOCK的套接字)上,是否可能发生部分写入(或部分发送)?
  • 在非阻塞套接字上,writev()和sendmsg()如何处理?由于部分写入向量(struct iovec [])处理起来有些麻烦,这非常重要。
  • 抱歉我的英语不好。

    1
    为了避免这种情况,许多人只是使用一个 writeall() 包装器,它会继续写入任何剩余的字节,直到所有内容都被写入。 - technosaurus
    writeall()只是我提供的循环。但是像writev_all()这样的函数更加复杂。当然,实现writev_all()并不需要太多工作,但对于非阻塞或阻塞文件来说,它是否必要呢? - Lv Zheng
    这是一个常用的示例:https://github.com/landley/toybox/blob/master/lib/lib.c#L79 - technosaurus
    写入普通文件时,如果出现“磁盘已满”或“超过配额”的错误怎么办?我认为您会先获得部分成功。 - o11c
    1
    这里有处理部分 writev 的好东西:https://dev59.com/pG025IYBdhLWcg3wsIIU - Zan Lynx
    3个回答

    5

    好的。由于标准没有提供任何保证,因此我们不能假设有完全的write()

    我谷歌了部分写入v并得到了一个答案:

    http://developerweb.net/viewtopic.php?id=4154

    是的,我之前也看到过这种行为(尽管是与sendmsg()及其iovecs一起使用时)... 实际上,这不是不正确/意外的行为... read()/recv()和write()/send()(以及I/O函数的所有排列组合)都可以返回短读取/写入,并且所有套接字代码都需要准备好处理它...无论它们是阻塞还是非阻塞模式套接字...所有这些只控制缓冲区完全为空(对于输入)或完全满(对于输出)时发生的情况...但是当发送缓冲区不太满时,通过阻止或非阻止套接字的任何写入超过剩余可用空间的数量将写入尽可能多的内容,然后返回短写计数...您应该预计需要再次调用它,以发送剩余量...使用普通的write()/send(),这很容易处理,但是使用writev()/sendmsg() iovecs时,处理起来确实变得棘手和麻烦...但是,您仍然必须这样做。

    writev_all()无法避免。

    谢谢。


    1
    https://dev59.com/pG025IYBdhLWcg3wsIIU - Zan Lynx

    -1
    “partial write()”(或“partial send()”)是否可能在常规文件上发生?
    是的。
    “(或未设置O_NONBLOCK的)套接字呢?”
    是的。
    “对于非阻塞套接字上的writev()和sendmsg()呢?”
    是的。
    “这非常重要,因为处理部分写入的向量(struct iovec [])有点麻烦。”
    不完全是。您知道写入了多少字节,只需相应地推进指针并递减大小即可。如果这对您来说太麻烦,请使用阻塞模式。

    在Linux中,即使是普通文件,也可以进行部分write()操作,例如当进程超出其资源限制(RLIMIT_FSIZE)或底层媒体空间不足时。 - Nominal Animal
    @Nominal 是的,谢谢你,我误读了问题,将其应用于非阻塞模式下的文件。 - user207421

    -1

    部分的 write() (或部分的 send()) 可能会发生在常规文件中 (或未设置O_NONBLOCK选项的套接字)。

    可能会。信号可以打断任何 I/O 操作,但库将根据 SA_RESTART 标志自动重新启动:

    此标志影响可中断函数的行为,即指定以设置 errno 为 [EINTR] 失败的函数。如果设置并且被此信号中断的可中断函数将重新启动,并且除非另有说明,否则不会以 [EINTR] 失败。如果使用超时的可中断函数重新启动,则在重新启动后,超时的持续时间设置为不超过原始超时值的未指定值。如果未设置该标志,那么被此信号中断的可中断函数将以设置 errno 为 [EINTR] 的方式失败。

    Linux 默认情况下设置了此标志,因此除非存在自定义信号处理,否则无需担心。

    NONBLOCK 套接字上的 writev()sendmsg() 怎么样?这非常重要,因为处理部分写入的向量 (struct iovec []) 有点麻烦。

    更复杂的系统调用也是一样处理的。用户空间库代码会在信号处理程序(如果有)返回后重新启动被中断的调用。


    @EJP C语言没有定义内核绑定。您调用一个C函数,该函数调用内核,等待一段时间,然后决定是否返回“EINTR”。实现SA_RESTART需要以某种方式捕获EINTR条件,但是该捕获只能在用户空间中发生,因为内核不知道信号处理标志。 - Potatoswatter
    你编辑中描述的“繁重工作”是不正确的。系统调用不会重新启动。该函数返回-1,并将errno设置为EINTR。否则,man页面不会将EINTR列为可能的错误之一。 - user207421
    @EJP,这个答案已经解释了。只有在未设置SA_RESTART的情况下,EINTR才是可能的。你的主张是什么?你否认有任何重新启动调用的事情吗? - Potatoswatter
    @Potatoswatter 在许多平台上,内核确实知道有关信号处理标志的信息。 - fuz
    @FUZxxl 是的,但大多数平台在完成信号处理程序后不希望进入内核。无论如何,这并不重要。如果系统调用被中断或者本来就会被中断,它都会重新启动,无论其复杂性如何。 - Potatoswatter
    显示剩余4条评论

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接