Chinaunix首页 | 论坛 | 博客
  • 博客访问: 40826
  • 博文数量: 10
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 90
  • 用 户 组: 普通用户
  • 注册时间: 2012-10-05 14:37
文章分类

全部博文(10)

文章存档

2015年(1)

2014年(1)

2013年(8)

我的朋友

分类: LINUX

2014-10-13 15:35:57

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

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

repeat:

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

{

        if(errno == EINTR)

                goto repeat;

      else

               printf("read failed");

}

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


  1. #define _GNU_SOURCE  
  2.   
  3. #include   
  4. #include   
  5. #include   
  6. #include   
  7. #include   
  8. #include   
  9. #include   
  10.   
  11. #include   
  12. #include   
  13.   
  14. void hup_handler(int sig)  
  15. {  
  16.         printf(".");  
  17.         fflush(stdout);  
  18. }  
  19.   
  20. int main()  
  21. {  
  22.         int i = 0;  
  23.         struct sigaction act;  
  24.         const int buffSize = 1 << 27;   
  25.         int allocated = 0;  
  26.         char* buf = NULL;  
  27.   
  28.         act.sa_handler = hup_handler;  
  29.         act.sa_flags = SA_INTERRUPT;  
  30.         sigemptyset(&act.sa_mask);  
  31.         allocated = posix_memalign((void**)&buf, getpagesize(), buffSize);  
  32.         if (0 != allocated)  
  33.         {  
  34.             perror("posix_memalign error");  
  35.             exit(1);  
  36.         }  
  37.   
  38.         sigaction(SIGHUP, &act, NULL);  
  39.         int fd = open("testfile", O_RDWR | O_DIRECT);  
  40.         //for (i = 0; i < 1; ++i)  
  41.         for (; ;)  
  42.         {  
  43.             if (lseek(fd, 0, SEEK_SET) == -1)  
  44.             {  
  45.                 printf("lseek failed: %s\n", strerror(errno));  
  46.             }  
  47.             if (read(fd, buf, buffSize) != buffSize)  
  48.             {  
  49.                 printf("read failed: %s\n", strerror(errno));  
  50.             }  
  51.         }  
  52. }  
代码中注册了信号SIG_HUP的信号处理函数,收到信号的时候应该会进入该处理函数。使用了O_DIRECT方式直接从磁盘读,然后运行该程序。在另外一个终端发送信号: while true; do pkill -HUP read; done

观察read进程,发现read确实进入了信号处理函数(终端输出了"......")但是,程序并没有打印出“read failed”错误,这与我们的预期不太符合,测试发现调用write接口时,现象也一样。为了进一步验证,我尝试去read终端设备,然后发信号,整个流程跟上面read测试基本相同,测试代码如下:


  1. #include   
  2. #include   
  3. #include   
  4. #include   
  5. #include   
  6. #include   
  7. #include   
  8. #include   
  9.   
  10.   
  11.   
  12.   
  13. void int_handler(int signum)  
  14. {  
  15.     printf("....\n");  
  16. }  
  17.   
  18.   
  19.   
  20. int main()  
  21. {  
  22.     char buf[128];  
  23.     ssize_t ret;  
  24.     struct sigaction oldact;  
  25.     struct sigaction act;  
  26.   
  27.     act.sa_handler = int_handler;  
  28.     //act.sa_flags = SA_INTERRUPT;  
  29.     act.sa_flags = SA_RESTART;  
  30.     sigemptyset(&act.sa_mask);  
  31.   
  32.     if(-1 == sigaction(SIGHUP, &act, &oldact))  
  33.     {  
  34.         exit(1);  
  35.     }  
  36.   
  37.     bzero(buf, 100);  
  38.   
  39.     while(true)  
  40.     {  
  41.         ret = read(STDIN_FILENO, buf, 10);  
  42.         if(-1 == ret)   
  43.         {  
  44.             perror("read terminal");  
  45.             //printf("read error%s\n"), strerror(errno);  
  46.         }  
  47.     }  
  48.          
  49.     return 0;  
  50. }  
编译运行该函数,然后在另外一个终端为其发送信号: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。
阅读(1379) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~