转自: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。

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

  1. EINTR错误

    慢系统调用(slow system call):此术语适用于那些可能永远阻塞的系统调用.永远阻塞的系统调用是指调用有可能永远无法返回,多数网络支持函数都属于这一类.如:若没有客户连接到服务器上,那么服 ...

  2. 关于 promise 吃到错误的理解

    关于 promise 吃到错误的理解 下面的内容需要对浏览器原生支持的 promise 的基本用法有了解,如果你还不知道 promise 和 promise 的 catch 方法,你可能需要先在 这里 ...

  3. 错误的理解引起的bug async await 执行顺序

    今天有幸好碰到一个bug,让我知道了之前我对await async 的理解有点偏差. 错误的理解 之前我一直以为  await 后面的表达式,如果是直接返回一个具体的值就不会等待,而是继续执行asyn ...

  4. linux中对EINTR错误的处理

    https://www.cnblogs.com/flyfish10000/articles/2576885.html EINTR错误的产生:当阻塞于某个慢系统调用的一个进程捕获某个信号且相应信号处理函 ...

  5. Java中的static(1)【持续更新】——关于Eclipse的No enclosing instance of type ... 错误的理解和改正

    No enclosing instance of type SomeClass is accessible. Must qualify the allocation with an enclosing ...

  6. 对express中引入文件时提示Error: Cannot find module错误的理解

    打算写个小demo,在引入一个routes文件时,一直提示Error: Cannot find module('./routes')的错误,经过一番了解. 如果要把整个文件夹下所有的模块都引进来  v ...

  7. OpenStack学习系列-----第二篇 由一个错误看理解整个架构的重要性

    看了openstack没几天,然后就开始试着用Java调用所有的API,第一步得到Credentials的时候成功了,然后第二步,传参数使所有的server信息都列出来的时候报错404.具体描述如下( ...

  8. 简单回射程序之处理accept返回EINTR错误的服务器程序版本

    #include <stdio.h> #include <stdlib.h> #include <time.h> #include <errno.h> ...

  9. DDD学习笔录——领域驱动设计的常见误区(即错误的理解)

    可以将DDD看成一种开发思想体系:它促成了一种新的以领域为中心的思维方式. 它是一种学习过程,而非最终目标,这就是DDD的最大优势. 任何团队都可以编写一个软件来满足一组用例的需求,但那些将时间和精力 ...

随机推荐

  1. spring boot 打包问题

    一.jar包 1.maven build package 2.linux 下执行 java -jar & 命令后台运行,也可加入服务运行 二.war包 1.将pom中的<packagin ...

  2. 关于Scala文件操作中出现的问题

    在各种项目中,我们常常需要用到文件操作,笔者在近期的项目中遇到了一个与文件操作相关的问题. 在代码实现的过程中,笔者首先定义了一个文件路径:def PATH = "/a/b/c.txt&qu ...

  3. 核方法(Kernel Methods)

    核方法(Kernel Methods) 支持向量机(SVM)是机器学习中一个常见的算法,通过最大间隔的思想去求解一个优化问题,得到一个分类超平面.对于非线性问题,则是通过引入核函数,对特征进行映射(通 ...

  4. 移动端webapp如何隐藏浏览器的导航栏

    webapp如何隐藏浏览器的导航栏 在webapp开发中,手机浏览器的导航栏会让我们的页面看起来很怪异,这个时候我们就需要将导航栏给隐藏起来,隐藏的方法十分简单,只需要在head头中加入以下几行代码就 ...

  5. Week2 Teamework from Z.XML 软件分析与用户需求调查(三)必应助手体验评测

    评测人:毛宇 肖俊鹏 说明:言辞激烈,请勿介意 我花了2天的时间来试用这个软件<必应缤纷桌面手机助手>,有了很多体会,这里,我来谈一下这款软件在体验部分的表现情况. 体验部分主要分为三个部 ...

  6. Java IO流-File类的使用示例-创建文件夹和文件的正确方法

    当创建一个文件时,比如:E:\\test\\test.txt,此时若文件夹test不存在,那么直接创建文件会出错,故首先要判断文件夹是否存在,不存在的话要首先创建文件夹. public class F ...

  7. beta版本冲刺七

    目录 组员情况 组员1:胡绪佩 组员2:胡青元 组员3:庄卉 组员4:家灿 组员5:恺琳 组员6:翟丹丹 组员7:何家伟 组员8:政演 组员9:黄鸿杰 组员10:何宇恒 组员11:刘一好 展示组内最新 ...

  8. 编程练习:寻找发帖"水王"扩展问题二

    回顾 在前面两篇文章已经实现了水王id出现次数超过一半,以及水王id出现次数刚好一半 分析 借助上面水王id出现次数刚好出现一半的分析,其实这里就是找出数组中出现次数前三的元素,具体的分析,见前面两篇 ...

  9. PAT 1089 狼人杀-简单版

    https://pintia.cn/problem-sets/994805260223102976/problems/1038429385296453632 以下文字摘自<灵机一动·好玩的数学& ...

  10. [剑指Offer] 27.字符串的排列

    [思路]从第一位开始,判断每一位字符的所有可能性,依此递归. class Solution { public: void PermutationHelp(vector<string> &a ...