关于EINTR错误的理解【转】

转自:http://www.xuebuyuan.com/1470645.html

最近在工作中遇到了EINTR错误,感到比较困惑,几番研究之后,颇有心得和收获,特记录如下,便于以后查询,也给有同样困惑的朋友们提供一点借鉴。

        我们经常在网络编程中会看到这样,当执行一个可能会阻塞的系统调用后,在返回的时候需要检查下错误码(if errno == EINTR),如果是这样的错误,那我们一般会重新执行该系统调用。所以经常的写法是:

repeat:

if(read(fd, buff, size) < 0)

{

        if(errno == EINTR)

                goto repeat;

      else

               printf("read failed");

}

但一般我们在读/写磁盘文件的时候却不太会判断这个错误,那我们到底什么时候该判断而什么时候又不要去判断呢?这是个问题。针对这个问题我特意做了一些测试。首先是读磁盘文件,测试代码如下:

#define _GNU_SOURCE

#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>
#include <sys/stat.h>

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

int main()
{
        int i = 0;
        struct sigaction act;
        const int buffSize = 1 << 27;
        int allocated = 0;
        char* buf = NULL;

        act.sa_handler = hup_handler;
        act.sa_flags = SA_INTERRUPT;
        sigemptyset(&act.sa_mask);
        allocated = posix_memalign((void**)&buf, getpagesize(), buffSize);
        if (0 != allocated)
        {
            perror("posix_memalign error");
            exit(1);
        }

        sigaction(SIGHUP, &act, NULL);
        int fd = open("testfile", O_RDWR | O_DIRECT);
        //for (i = 0; i < 1; ++i)
        for (; 

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

代码中注册了信号SIG_HUP的信号处理函数,收到信号的时候应该会进入该处理函数。使用了O_DIRECT方式直接从磁盘读,然后运行该程序。在另外一个终端发送信号

while true; do pkill -HUP read; done

观察read进程,发现read确实进入了信号处理函数(终端输出了"......")但是,程序并没有打印出“read failed”错误,这与我们的预期不太符合,测试发现调用write接口时,现象也一样。

为了进一步验证,我尝试去read终端设备,然后发信号,整个流程跟上面read测试基本相同,测试代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <stdbool.h>

void int_handler(int signum)
{
    printf("....\n");
}

int main()
{
    char buf[128];
    ssize_t ret;
    struct sigaction oldact;
    struct sigaction act;

    act.sa_handler = int_handler;
    //act.sa_flags = SA_INTERRUPT;
    act.sa_flags = SA_RESTART;
    sigemptyset(&act.sa_mask);

    if(-1 == sigaction(SIGHUP, &act, &oldact))
    {
        exit(1);
    }

    bzero(buf, 100);

    while(true)
    {
        ret = read(STDIN_FILENO, buf, 10);
        if(-1 == ret)
        {
            perror("read terminal");
            //printf("read error%s\n"), strerror(errno);
        }
    }

    return 0;
}

编译运行该函数,然后在另外一个终端为其发送信号:

while true; do pkill -HUP read; done

然后观察发现,第一终端处理函数确实被执行了,然后read函数不断打印错误,“read terminal:Interuptible system call”。发现read函数确实在执行成功以前被信号中断了,返回EINTR错误。

这种情况在read socket也同样出现。那为什么read磁盘文件不会出现上面的情况呢?

为了理解各种原因,我们首先来看看linux的信号处理流程。

一般来说,信号处理函数会在进程执行系统或者库函数调用退出时刻被执行,也就是说,进程的信号处理是在系统调用从内核态返回至用户态之前被执行的,如果该进程收到了信号的话。对于一直处于RUNNING态的进程来说,会在系统调用执行完成后,返回用户态前夕执行信号处理函数。

但是,如果进程在内核态进行了状态转换,这时候处理流程就有点微妙的变化了。如由于等待某些事件的发生(最典型的IO等待),进程可能会从RUNNING状态转变为休眠状态,休眠状态的进程会被切换出CPU。但休眠状态有两种:第一是INTERUPTIBLE进程,第二种UNINTERUPTIBLE进程。第一种进程是可被信号唤醒的进程,第二种是不可被信号唤醒的进程,这就是问题的关键。对于INTERUPTIBLE状态的进程,一旦被信号唤醒后,会退出内核态执行,退出内核态之前执行信号处理函数。如果资源没准备好,那此时可能会设置错误码为EINTR。但是对于处于UNTERUPTIBLE状态的进程,该进程是不可被信号唤醒的,也就是说,当进程休眠时,会屏蔽所有的信号,直到它从休眠状态返回至RUNNING状态,执行完成后,返回上次执行的地方继续运行,然后退出内核态时候执行所有信号的处理函数。也就是说,这个状态的进程应该是不会被信号中断的,只会等到资源准备妥当时候才会被唤醒,这时候应该不存在会返回EINTR错误的情况。

为了验证这种情况,我们使用ps -aux查看了上述几种情况下进程所处的状态。首先是读磁盘,ps-aux发现其显示状态为D(即UNTERUPTIBLE),接下来读terminal,ps -aux显示其状态为S(INTERUPTIBLE)。

这就能解释上述两种情况显示的现象不一致的原因了。read磁盘的时候进程是处于UNINTERUPTIBLE状态,没法被信号唤醒,只能等到read到的数据准备好的时候被唤醒,这个时候再从内核态返回至用户态处理信号处理函数时,并不会出现EINTR错误。

而对于read读终端,情况则不一样,它处于INTERUPTIBLE状态,当被信号唤醒时,会直接退出内核态,此时应该提醒用户态资源并没有准备好,因此应该返回EINTR错误。以便用户态可以做出自己的决定。

备注:如果不想内核在系统调用返回EINTR错误,那么可以将信号处理函数的标记位设置SA_RESTART。

时间: 2024-11-03 23:10:27

关于EINTR错误的理解【转】的相关文章

大多数营销人都错误地理解了APP营销

摘要: 移动应用的爆炸式增长,使数字营销达到了前所未有的高度,在移动互联网时代,拥有一款APP成为接触和拉拢消费者的有效途径.尼尔森的一份报告显示,APP已经占据了智能手机用户使 移动应用的爆炸式增长,使数字营销达到了前所未有的高度,在移动互联网时代,拥有一款APP成为接触和拉拢消费者的有效途径.尼尔森的一份报告显示,APP已经占据了智能手机用户使用手机时间的86%,如此巨大机会,营销人自然不会放过.目前,47%的营销机构和企业已经拥有了自己的APP,65%的营销人正在打算开发APP,APP营销

Delphi:对TMemoryStream.Memory错误的理解

TMemoryStream 的 Position 变化后, 我曾经认为它的 Memory 属性也会变化; 只怪不看源码, 只想当然! procedure TForm1.FormCreate(Sender: TObject); var Stream1,Stream2: TMemoryStream; pw: TPtrWrapper; begin Stream1 := TStringStream.Create('1234567890'); Stream2 := TStringStream.Create

Linux下semop等待信号时出现Interrupted System Call错误(EINTR)解决方法_C 语言

错误现象:(semop函数调用,strerror(errno)输出结果)Interrupted system call平台:RedHat Linux LINUX文档关于EINTR的描述是这样子的:  While blocked in this system call, the process caught a signal.UNIX文档[IEEE Std 1003.1-2008]关于EINTR的描述是这样子的:  The semop() function was interrupted by a

socket中的函数遇见EINTR的处理【转】

转自:http://blog.chinaunix.net/uid-21501855-id-4490453.html 这几天,写服务器代码过程当中,遇见EINRT信号的问题,我是借鉴 <unp >,采用continue或者goto again循环解决的.但是感觉这个还是很有必要记录一下.网络上查找到的信息很多.下面是我查找到的和EINTR有关的介绍: 1  http://blog.csdn.net/yanook/article/details/7226019  慢系统调用函数如何处理中断信号EI

正确理解Traceback的含义

Traceback是Blog的一个重要特性,然而由于TraceBack的歧义性导致不少人都没有真正明白TraceBack的用途. Traceback Ping在blog系统中广泛使用,简单说来,Trackback是网站与网站之间互相通告的一种方法.例如,当你读了一篇日志,想对此写下自己的感想,您可以把新的日志内容写到自己的博客上.然后向原来的那篇日志发送一个引用通告.通过这种办法,在原始文章的下面就留下了你自己博客中的日志的链接,这样对于同一个话题的讨论,可以不局限在一个博客中了. Traceb

全面理解Java中的String数据类型

数据|数据类型 1. 首先String不属于8种基本数据类型,String是一个对象. 因为对象的默认值是null,所以String的默认值也是null:但它又是一种特殊的对象,有其它对象没有的一些特性. 2. new String()和new String("")都是申明一个新的空字符串,是空串不是null: 3. String str="kvill": String str=new String ("kvill");的区别: 在这里,我们不谈

谷歌谈五个常见的SEO错误给我们的启示

大家好,我是木子成舟.在chinaz的首页上看到一篇关于谷歌网站管理员博客中谈常见的五个SEO错误文章,个人感觉的确非常的好,谈得非常的全面,也是我们SEO工作者,乃至于企业进行SEO营销的时候会遇到的问题,其实这篇文章之前在夜息的SEO博客中也看到了,这个不属于新文章,应该算一篇老的文章,但是依然对SEO有指导意义,下面简单的谈谈自己对于这篇文章的理解. 1.缺少SEO价值定位 这是第一个SEO错误,缺少价值定位说的是我们对于SEO价值定位有不少的朋友从一开始就是错的,最常见的就是为了SEO而

深入理解计算机系统-之-内存寻址(一)--存储管理机制(虚拟地址,线性地址,物理地址)

物理地址(physical address) 用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应. 这个概念应该是这几个概念中最好理解的一个,但是值得一提的是,虽然可以直接把物理地址理解成插在机器上那根内存本身,把内存看成一个从0字节一直到最大空量逐字节的编号的大数组,然后把这个数组叫做物理地址,但是事实上,这只是一个硬件提供给软件的抽像,内存的寻址方式并不是这样. 所以,说它是"与地址总线相对应",是更贴切一些,不过抛开对物理内存寻址方式的考虑,直接把物理地址与物理的内存

《深入理解Hadoop(原书第2版)》——2.3Hadoop系统的组成

2.3Hadoop系统的组成 本节内容会细致深入地讲解Hadoop系统的各个组成部分.我们先介绍构成Hadoop 1.x 系统的组件,再介绍构成Hadoop 2.x 系统的新组件.宏观上说,Hadoop 1.x系统有以下几个守护进程: 名称节点(NameNode):维护着存储在HDFS上的所有文件的元数据信息.这些元数据信息包括组成文件的数据块信息,及这些数据块在数据节点上的位置.正如你将看到的,这个组件正是使用Hadoop 1.x搭建大型计算集群系统的瓶颈所在. 辅助名称节点(Secondar