为什么write(2)没有返回EINTR?

4
我一直在阅读有关write(2)中的,并尝试确定是否需要在我的程序中检查它。为了进行健全性检查,我尝试编写一个会遇到它的程序。该程序循环不停地重复向文件写入。
然后,在单独的shell中,我运行:
while true; do pkill -HUP test; done

然而,我从test.c看到的唯一输出是信号处理程序中的.。为什么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的写入大小的另一个“mmap”文件写入文件。 - David Schwartz
3个回答

10

Linux在对文件进行写入/读取时,往往会避免出现EINTR错误。关于此问题的讨论,请参见这里。当进程被阻塞在磁盘写操作上时,它可能会处于不可中断的睡眠状态(进程代码为D),表明此时无法中断该进程。这取决于设备驱动程序;Linux设备驱动程序第三版在线副本是了解内核方面的情况的良好参考。

但是,在其他平台或管道和套接字中仍然需要处理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。 - Rodrigo Queiro
要出现EINTR,你必须在进入write调用和第一个字节放入内部缓冲区之间的短时间内中断进程,这是非常不可能的事件。通过来自shell的kill循环实现这一点也是非常不可能的。 - Jens Gustedt
请注意,除非您正在安装中断信号处理程序(即缺少SA_RESTART标志)或尝试解决非常旧的Linux版本上的错误,否则您永远不需要处理EINTR - R.. GitHub STOP HELPING ICE

5
有两种可能性:
  • 您只写入了非常少的字节,因为您误用了sizeof运算符。 因此,write会立即发生并且从未被中断 - 您每次只写入4或8个字节

  • 一些系统调用会被重新启动,就好像您对sigaction应用了 SA_RESTART一样


在您的代码中,由于buf是一个指针,sizeof(buf)返回的是您计算机上指针的大小,而不是(更大的)分配空间的大小。


sizeof() 的错误你发现得很好:我最初将 buf 放在堆栈上,但忘记更改了。然而,修复这个问题并不会改变整体行为(除了使事情变慢)。 - Rodrigo Queiro

1

如果您查看EINTR手册页面

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

还有来自signal(7)手册页面的内容:

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

结合这两个信息,如果正在向磁盘上的文件写入,并且write已经开始写入(即使只写入了一个字节),那么该write调用的返回值将是成功的。


我的代码检查所有数据是否已写入,因此它可以检测到这种情况。 - Rodrigo Queiro
@RodrigoQueiro 是的,但它仍然不是一个“错误”,而且它特别不会导致EINTR。正如cnicutar所指出的那样,您错误地使用了sizeof运算符,因此write调用只写入四个或八个字节(取决于您是否在32位或64位平台上)。 - Some programmer dude

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