【问题标题】:Why isn't write(2) returning EINTR?为什么 write(2) 不返回 EINTR?
【发布时间】:2012-08-04 08:50:56
【问题描述】:

我一直在阅读write(2) 等关于EINTR 的内容,并试图确定是否需要在我的程序中检查它。作为健全性检查,我尝试编写一个会运行它的程序。程序永远循环,重复写入文件。

然后,在一个单独的 shell 中,我运行:

while true; do pkill -HUP test; done

但是,我从 test.c 看到的唯一输出是来自信号处理程序的 .s。为什么SIGHUP 不会导致write(2) 失败?

test.c:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>

#include <sys/types.h>

void hup_handler(int sig)
{
    printf(".");
    fflush(stdout);
}

int main()
{
    struct sigaction act;
    act.sa_handler = hup_handler;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);

    sigaction(SIGHUP, &act, NULL);

    int fd = open("testfile", O_WRONLY);

    char* buf = malloc(1024*1024*128);

    for (;;)
    {
        if (lseek(fd, 0, SEEK_SET) == -1)
        {
            printf("lseek failed: %s\n", strerror(errno));
        }
        if (write(fd, buf, sizeof(buf)) != sizeof(buf))
        {
            printf("write failed: %s\n", strerror(errno));
        }
    }
}

【问题讨论】:

  • 由于这个write没有I/O,所以不可能中断I/O。您只是在修改缓存中的页面。尝试写入套接字、写入 NFS 服务器或使用大于 RAM 的写入大小从另一个 mmaped 文件写入文件。

标签: c linux


【解决方案1】:

Linux 倾向于避免EINTR 写入/读取文件;见discussion here。当进程阻塞磁盘写入时,它可能会处于uninterruptible sleep 状态(进程代码D),这表明它当时不能被中断。这取决于设备驱动程序; online copy of Linux Device Drivers, 3rd Edition 是一个很好的参考,可以从内核方面看到它。

您仍然需要为其他可能表现不同的平台处理 EINTR,或者对于肯定会发生 EINTR 的管道和套接字。

请注意,您一次只能写入 sizeof(void *) 个字节:

char* buf = malloc(1024*1024*128);

    if (write(fd, buf, sizeof(buf)) != sizeof(buf))

这应该是

const size_t BUF_SIZE = 1024*1024*128;
char* buf = malloc(BUF_SIZE);

    if (write(fd, buf, BUF_SIZE) != BUF_SIZE)

【讨论】:

  • 如果我将其更改为 FIFO,则写入不会完成,尽管它没有设置 EINTR:输出看起来像“.write failed: Success\n”重复。但是,我想我可以让 EINTR 在某个时候发生。
  • 要拥有 EINTR,您确实必须在进入 write 调用和将第一个字节放入内部缓冲区之间的短时间内中断进程,这是一个非常不可能的事件。通过 shell 的 kill 循环来实现这一点是不太可能的。
  • 另外请注意,您永远不需要处理EINTR,除非您正在安装中断信号处理程序(即缺少SA_RESTART 标志)或试图解决非常旧的Linux 版本上的错误。
【解决方案2】:

有两种可能:

  • 您写入的字节很少,因为您误用了sizeof 运算符。因此write 立即发生并且它永远不会被中断 - 你一次只写 4 或 8 个字节

  • 系统调用以某种方式重新启动,就好像您将 SA_RESTART 应用于 sigaction


在您的代码中,由于buf 是一个指针,sizeof(buf) 产生您机器上指针的大小,而不是(更大的)分配空间

【讨论】:

  • 很好地发现了 sizeof() 错误:我最初在堆栈上有 buf,但忘记更改它。但是,解决此问题不会改变整体行为(除了让事情变慢)。
【解决方案3】:

如果您检查 manual page 是否为 EINTR

在写入任何数据之前调用被信号中断

同样来自signal(7) manual page

read(2)、readv(2)、write(2)、writev(2) 和 ioctl(2) 调用“慢”设备。 “慢”设备是 I/O 调用可能无限期阻塞的设备,例如终端、管道或套接字。 (根据此定义,磁盘不是慢速设备。)如果慢速设备上的 I/O 调用在被信号处理程序中断时已经传输了一些数据,则调用将返回成功状态(通常,传输的字节数)。

将这两者结合起来,如果写入磁盘上的文件,并且write 已经开始写入(即使只写入了一个字节),那么来自write 调用的返回将是成功的。

【讨论】:

  • 我的代码检查所有数据是否已写入,因此它会检测到这种情况。
  • @RodrigoQueiro 是的,但它仍然不是 错误,并且它具体不会导致 EINTR 的原因。正如 cnicutar 所指出的,您错误地使用了 sizeof 运算符,因此 write 调用仅写入四个或八个字节(取决于您是在 32 位还是 64 位平台上)。
猜你喜欢
  • 1970-01-01
  • 2019-01-10
  • 1970-01-01
  • 1970-01-01
  • 2013-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-24
相关资源
最近更新 更多