【问题标题】:POSIX partial write() and Signal InterruptsPOSIX 部分 write() 和信号中断
【发布时间】:2020-08-26 08:55:03
【问题描述】:

来自write()的手册页

请注意,成功的 write() 可能传输少于 count 个字节。 这种部分写入可能由于各种原因而发生;例如, 因为磁盘设备上没有足够的空间来写所有 请求的字节数,或者因为 阻塞了对套接字的 write(), 管道,或类似的被信号处理程序打断 转移了一些,但在转移所有请求之前 字节。在部分写入的情况下,调用者可以进行另一个 write() 调用以传输剩余的字节。随后的调用 将传输更多字节或可能导致错误(例如, 如果磁盘现在已满)。

我有以下问题

1) 如果write() 在部分传输后被信号处理程序中断,write() 会将errno 设置为EINTR 吗?

2) 如果没有设置errno,有没有办法在没有额外代码的情况下识别这样的事件(比如安装信号处理并将标志值设置为true)?

注意:进一步的write()调用在信号中断事件后成功传输剩余字节。

【问题讨论】:

    标签: c linux network-programming posix


    【解决方案1】:

    回答您的个别编号问题:

    1. errno 仅在其中一个标准函数返回一个指示错误的值之后(对于write,-1)和任何其他可能破坏它的标准函数或应用程序代码被调用之前才有意义。所以不,如果write 返回一个短写,errno 将不会被设置为任何有意义的值。如果它等于EINTR,它恰好是;这不是您可以解释的有意义的事情。

    2. 识别此类事件的方法是返回值严格小于nbytes 参数。这实际上并没有告诉你短写的原因,所以它可能是其他的东西,比如空间不足。如果您需要知道,您需要安排信号处理程序通知您。但在几乎所有情况下,您实际上都不需要知道。

    关于注释,如果write 在信号到达后返回完整的nbytes,则信号处理程序不会中断。这是 Linux 上任何现代 libc(glibc、musl,基本上除了 libc5 之外的任何东西)的默认设置,而且它几乎总是正确的。如果你真的想要中断信号,你必须安装带有sigactionSA_RESTART 标志清除的信号处理程序。 (相反,如果您正在安装信号处理程序,您希望获得正常、合理、不中断的行为,为了可移植性,您应该使用sigaction 并设置SA_RESTART 标志而不是使用旧函数signal)。

    【讨论】:

    • 所以安装信号处理程序是一种方法。我可以考虑其他替代方案,例如阻塞信号并使用 signalfd() 和 select() 来捕获信号中断事件。注意,我的意思是进一步调用 write() 不会产生错误。谢谢!
    • @MohithReddy:你到底想做什么?您的问题是询问您可以期待看到什么,而不是您想要实现什么......
    • 我觉得你对第 2 部分的回答回避了这里更一般的问题:“如果 write 不写任何字节,errno 会给我一个关于为什么它不能写的线索写它们。如果write 写了一些字节但不是全部,有没有办法知道为什么它不能全部写?"
    • @JosephSible-ReinstateMonica:不,没有。但是除了被信号中断(这是混乱的并且您应该避免的事情)之外,大多数此类情况将在您下次调用 write 试图完成未完成的操作时看到。很多像EPIPE 这样的东西是粘性的,但像ENOSPC 这样的一些可能是暂时的并且受TOCTOU 问题的影响。
    【解决方案2】:

    让我们试试看:

    #define _GNU_SOURCE
    
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <unistd.h>
    #include <signal.h>
    
    static void handle_sigalrm(int sig) {
    }
    
    int main(void) {
        struct sigaction act;
        memset(&act, 0, sizeof act);
        act.sa_handler = handle_sigalrm;
        sigaction(SIGALRM, &act, NULL);
        int fds[2];
        pipe(fds);
        int bufsize = fcntl(fds[1], F_GETPIPE_SZ) + 10;
        char *buf = calloc(bufsize, 1);
        ssize_t written;
        printf("will attempt to write %d bytes and EINTR is %d\n", bufsize, EINTR);
        alarm(1);
        errno = 0;
        written = write(fds[1], buf, bufsize);
        printf("write returned %td and errno is %d\n", written, errno);
        return 0;
    }
    

    该程序创建了一个不会读取任何内容的管道,对它进行比内核缓冲区大的写入操作,并安排信号处理程序在写入阻塞时运行。在我的系统上,它会打印:

    will attempt to write 65546 bytes and EINTR is 4
    write returned 65536 and errno is 0
    

    因此,对于“如果 write() 在部分传输后被信号处理程序中断,write() 是否会将 errno 设置为 EINTR?”的答案。是“不,它不会”。

    【讨论】:

    • 是的,得到了​​同样的结果!所以需要一段额外的代码来识别这样的事件。谢谢!
    猜你喜欢
    • 2011-02-04
    • 2023-03-10
    • 2012-02-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多