Linux中断的系统调用

早期UNIX系统的一个特性是:如果在进程执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断不再继续执行。该系统调用返回出错,其errno设置为EINTR。这样处理的理由是:因为一个信号发生了,进程捕捉到了它,这意味着已经发生了某种事情,所以是个好机会应当唤醒阻塞的系统调用。

在这里,我们必须区分系统调用和函数。当捕捉到某个信号时,被中断的是内核中的执行的系统调用。

为了支持这种特性,将系统调用分成两类:低速系统调用和其他系统调用。低速系统调用是可能会使进程永远阻塞的一类系统调用,它们包括:

  • 在读某些类型的文件时,如果数据并不存在则可能会使调用者永远阻塞(管道、终端设备以及网络设备)。
  • 在写这些类型的文件时,如果不能立即接受这些数据,则也可能会使调用者永远阻塞。
  • 打开文件,在某种条件发生之前也可能会使调用者阻塞(例如,打开终端设备,它要等待直到所连接的调制解调器回答了电话)。
  • pause(按照定义,它使调用进程睡眠直至捕捉到一个信号)和wait。
  • 某种ioctl操作。
  • 某些进程间通信函数

在这些低速系统调用中一个值得注意的例外是与磁盘I / O有关的系统调用。虽然读、写一个磁盘文件可能暂时阻塞调用者(在磁盘驱动程序将请求排入队列,然后在适当时间执行请求期间),但是除非发生硬件错误,I / O操作总会很快返回,并使调用者不再处于阻塞状态。

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

EINTR错误的产生:当阻塞于某个慢系统调用的一个进程捕获某个信号且相应信号处理函数返回时,该系统调用可 能返回一个EINTR错误。例如:在socket服务器端,设置了信号捕获机制,有子进程,当在父进程阻塞于慢系统调用时由父进程捕获到了一个有效信号 时,内核会致使accept返回一个EINTR错误(被中断的系统调用)。

当碰到EINTR错误的时候,可以采取有一些可以重启的系统调
用要进行重启,而对于有一些系统调用是不能够重启的。例如:accept、read、write、select、和open之类的函数来说,是可以进行重
启的。不过对于套接字编程中的connect函数我们是不能重启的,若connect函数返回一个EINTR错误的时候,我们不能再次调用它,否则将立即
返回一个错误。针对connect不能重启的处理方法是,必须调用select来等待连接完成

为了帮助应用程序使其不必处理被中断的系统调用,4.2BSD引进了某些被中断的系统调用的自动再起动。自动再起动的系统调用包括:ioctl、
read、readv、write、writev、wait和waitpid。正如前述,其中前五个函数只有对低速设备进行操作时才会被信号中断。而
wait和waitpid在捕捉到信号时总是被中断。某些应用程序并不希望这些函数被中断后再起动,因为这种自动再起动的处理方式也会带来问题,为此
4.3BSD允许进程在每个信号各别处理的基础上不使用此功能。

怎么看哪些系统条用会产生EINTR错误呢?用man啊!

http://man7.org/linux/man-pages/man7/signal.7.html

Interruption of system calls and library functions by signal handlers
       If a signal handler is invoked while a system call or library
       function call is blocked, then either:

       * the call is automatically restarted after the signal handler
         returns; or

       * the call fails with the error EINTR.

       Which of these two behaviors occurs depends on the interface and
       whether or not the signal handler was established using the
       SA_RESTART flag (see sigaction(2)).  The details vary across UNIX
       systems; below, the details for Linux.

       If a blocked call to one of the following interfaces is interrupted
       by a signal handler, then the call will be automatically restarted
       after the signal handler returns if the SA_RESTART flag was used;
       otherwise the call will fail with the error EINTR:

           * read(2), readv(2), write(2), writev(2), and ioctl(2) calls on
             "slow" devices.  A "slow" device is one where the I/O call may
             block for an indefinite time, for example, a terminal, pipe, or
             socket.  (A disk is not a slow device according to this
             definition.)  If an I/O call on a slow device has already
             transferred some data by the time it is interrupted by a signal
             handler, then the call will return a success status (normally,
             the number of bytes transferred).

           * open(2), if it can block (e.g., when opening a FIFO; see
             fifo(7)).

           * wait(2), wait3(2), wait4(2), waitid(2), and waitpid(2).

           * Socket interfaces: accept(2), connect(2), recv(2), recvfrom(2),
             recvmmsg(2), recvmsg(2), send(2), sendto(2), and sendmsg(2),
             unless a timeout has been set on the socket (see below).

           * File locking interfaces: flock(2) and fcntl(2) F_SETLKW.

           * POSIX message queue interfaces: mq_receive(3),
             mq_timedreceive(3), mq_send(3), and mq_timedsend(3).

           * futex(2) FUTEX_WAIT (since Linux 2.6.22; beforehand, always
             failed with EINTR).

           * POSIX semaphore interfaces: sem_wait(3) and sem_timedwait(3)
             (since Linux 2.6.22; beforehand, always failed with EINTR).

       The following interfaces are never restarted after being interrupted
       by a signal handler, regardless of the use of SA_RESTART; they always
       fail with the error EINTR when interrupted by a signal handler:

           * "Input" socket interfaces, when a timeout (SO_RCVTIMEO) has
             been set on the socket using setsockopt(2): accept(2), recv(2),
             recvfrom(2), recvmmsg(2) (also with a non-NULL timeout
             argument), and recvmsg(2).

           * "Output" socket interfaces, when a timeout (SO_RCVTIMEO) has
             been set on the socket using setsockopt(2): connect(2),
             send(2), sendto(2), and sendmsg(2).

           * Interfaces used to wait for signals: pause(2), sigsuspend(2),
             sigtimedwait(2), and sigwaitinfo(2).

           * File descriptor multiplexing interfaces: epoll_wait(2),
             epoll_pwait(2), poll(2), ppoll(2), select(2), and pselect(2).

           * System V IPC interfaces: msgrcv(2), msgsnd(2), semop(2), and
             semtimedop(2).

           * Sleep interfaces: clock_nanosleep(2), nanosleep(2), and
             usleep(3).

           * read(2) from an inotify(7) file descriptor.

           * io_getevents(2).

       The sleep(3) function is also never restarted if interrupted by a
       handler, but gives a success return: the number of seconds remaining
       to sleep.

   Interruption of system calls and library functions by stop signals
       On Linux, even in the absence of signal handlers, certain blocking
       interfaces can fail with the error EINTR after the process is stopped
       by one of the stop signals and then resumed via SIGCONT.  This
       behavior is not sanctioned by POSIX.1, and doesn‘t occur on other
       systems.

       The Linux interfaces that display this behavior are:

           * "Input" socket interfaces, when a timeout (SO_RCVTIMEO) has
             been set on the socket using setsockopt(2): accept(2), recv(2),
             recvfrom(2), recvmmsg(2) (also with a non-NULL timeout
             argument), and recvmsg(2).

           * "Output" socket interfaces, when a timeout (SO_RCVTIMEO) has
             been set on the socket using setsockopt(2): connect(2),
             send(2), sendto(2), and sendmsg(2), if a send timeout
             (SO_SNDTIMEO) has been set.

           * epoll_wait(2), epoll_pwait(2).

           * semop(2), semtimedop(2).

           * sigtimedwait(2), sigwaitinfo(2).

           * read(2) from an inotify(7) file descriptor.

           * Linux 2.6.21 and earlier: futex(2) FUTEX_WAIT,
             sem_timedwait(3), sem_wait(3).

           * Linux 2.6.8 and earlier: msgrcv(2), msgsnd(2).

           * Linux 2.4 and earlier: nanosleep(2).

处理被中断的系统调用

既然系统调用会被中断,那么别忘了要处理被中断的系统调用。有三种处理方式:

◆ 人为重启被中断的系统调用

◆ 安装信号时设置 SA_RESTART属性(该方法对有的系统调用无效)

◆  忽略信号(让系统不产生信号中断)

1. 人为重启被中断的系统调用

人为当碰到EINTR错误的时候,有一些可以重启的系统调用要进行重启,而对于有一些系统调用是不能够重启的。例如:accept、read、 write、select、和open之类的函数来说,是可以进行重启的。不过对于套接字编程中的connect函数我们是不能重启的,若connect 函数返回一个EINTR错误的时候,我们不能再次调用它,否则将立即返回一个错误。针对connect不能重启的处理方法是,必须调用select来等待 连接完成。

这里的“重启”怎么理解?

一些IO系统调用执行时,如 read 等待输入期间,如果收到一个信号,系统将中断read, 转而执行信号处理函数. 当信号处理返回后, 系统遇到了一个问题: 是重新开始这个系统调用, 还是让系统调用失败?早期UNIX系统的做法是, 中断系统调用,并让系统调用失败, 比如read返回 -1, 同时设置 errno 为EINTR中断了的系统调用是没有完成的调用,它的失败是临时性的,如果再次调用则可能成功,这并不是真正的失败,所以要对这种情况进行处理, 典型的方式为:

again:
          if ((n = read(fd, buf, BUFFSIZE)) < 0) {
             if (errno == EINTR)
                  goto again;     /* just an interrupted system call */
            /* handle other errors */
          }

for( ; ;) {
     if (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0)
    {
    if (errno == EINTR)
        continue;
    }
    else
    {
        errsys("accept error");
    }
}

2. 安装信号时设置 SA_RESTART属性

我们还可以从信号的角度来解决这个问题,  安装信号的时候, 设置 SA_RESTART属性,那么当信号处理函数返回后, 不会让系统调用返回失败,而是让被该信号中断的系统调用将自动恢复。

struct sigaction action;

action.sa_handler = handler_func;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
/* 设置SA_RESTART属性 */
action.sa_flags |= SA_RESTART;

sigaction(SIGALRM, &action, NULL);

但注意,并不是所有的系统调用都可以自动恢复。如msgsnd喝msgrcv就是典型的例子,msgsnd/msgrcv以block方式发送/接 收消息时,会因为进程收到了信号而中断。此时msgsnd/msgrcv将返回-1,errno被设置为EINTR。且即使在插入信号时设置了 SA_RESTART,也无效。在man msgrcv中就有提到这点:

(msgsnd and msgrcv are never automatically restarted after being interrupted by a signal handler, regardless of the setting of the SA_RESTART flag when establishing a signal handler.)

3. 忽略信号

当然最简单的方法是忽略信号,在安装信号时,明确告诉系统不会产生该信号的中断。

struct sigaction action;

action.sa_handler = SIG_IGN;
sigemptyset(&action.sa_mask);

sigaction(SIGALRM, &action, NULL);

测试代码

测试代码一

闹钟信号SIGALRM中断read系统调用。安装SIGALRM信号时如果不设置SA_RESTART属性,信号会中断read系统过调用。如果设置了SA_RESTART属性,read就能够自己恢复系统调用,不会产生EINTR错误。

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <string.h>
#include <unistd.h>

void sig_handler(int signum)
{
    printf("in handler\n");
    sleep(1);
    printf("handler return\n");
}

int main(int argc, char **argv)
{
    char buf[100];
    int ret;
    struct sigaction action, old_action;

    action.sa_handler = sig_handler;
    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;
    /* 版本1:不设置SA_RESTART属性
     * 版本2:设置SA_RESTART属性 */
    //action.sa_flags |= SA_RESTART;

    sigaction(SIGALRM, NULL, &old_action);
    if (old_action.sa_handler != SIG_IGN) {
        sigaction(SIGALRM, &action, NULL);
    }
    alarm(3);

    bzero(buf, 100);

    ret = read(0, buf, 100);
    if (ret == -1) {
        perror("read");
    }

    printf("read %d bytes:\n", ret);
    printf("%s\n", buf);

    return 0;
}

测试代码二

闹钟信号SIGALRM中断msgrcv系统调用。即使在插入信号时设置了SA_RESTART,也无效。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

void ding(int sig)
{
    printf("Ding!\n");
}

struct msgst
{
    long int msg_type;
    char buf[1];
};

int main()
{
    int nMsgID = -1;

    // 捕捉闹钟信息号
    struct sigaction action;
    action.sa_handler = ding;
    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;
    // 版本1:不设置SA_RESTART属性
    // 版本2:设置SA_RESTART属性
    action.sa_flags |= SA_RESTART;
    sigaction(SIGALRM, &action, NULL);

    alarm(3);
    printf("waiting for alarm to go off\n");

    // 新建消息队列
    nMsgID = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
    if( nMsgID < 0 )
    {
        perror("msgget fail" );
        return;
    }
    printf("msgget success.\n");

    // 阻塞 等待消息队列
    //
    // msgrcv会因为进程收到了信号而中断。返回-1,errno被设置为EINTR。
    // 即使在插入信号时设置了SA_RESTART,也无效。man msgrcv就有说明。
    //
    struct msgst msg_st;
    if( -1 == msgrcv( nMsgID, (void*)&msg_st, 1, 0, 0 ) )
    {
        perror("msgrcv fail");
    }

    printf("done\n");

    exit(0);
}

总结

慢系统调用(slow system call)会被信号中断,系统调用函数返回失败,并且errno被置为EINTR(错误描述为“Interrupted system call”)。

处理方法有以下三种:①人为重启被中断的系统调用;②安装信号时设置 SA_RESTART属性;③忽略信号(让系统不产生信号中断)。

有时我们需要捕获信号,但又考虑到第②种方法的局限性(设置 SA_RESTART属性对有的系统无效,如msgrcv),所以在编写代码时,一定要“人为重启被中断的系统调用”。

非阻塞(nonblock)socket接口会否出现EINTR错误

对于socket接口(指connect/send/recv/accept..等等后面不重复,不包括不能设置非阻塞的如select),在阻塞模式下有可能因为发生信号,返回EINTR错误,由用户做重试或终止。

但是,在非阻塞模式下,是否出现这种错误呢? 
对此,重温了系统调用、信号、socket相关知识,得出结论是:不会出现

首先, 
1.信号的处理是在用户态下进行的,也就是必须等待一个系统调用执行完了才会执行进程的信号函数,所以就有了信号队列保存未执行的信号 
2.用户态下被信号中断时,内核会记录中断地址,信号处理完后,如果进程没有退出则重回这个地址继续执行

socket接口是一个系统调用,也就是即使发生了信号也不会中断,必须等socket接口返回了,进程才能处理信号。
也就是,EINTR错误是socket接口主动抛出来的,不是内核抛的。socket接口也可以选择不返回,自己内部重试之类的..

那阻塞的时候socket接口是怎么处理发生信号的?

举例 
socket接口,例如recv接口会做2件事情, 
1.检查buffer是否有数据,有则复制清除返回 
2.没有数据,则进入睡眠模式,当超时、数据到达、发生错误则唤醒进程处理

socket接口的实现都差不了太多,抽象说 
1.资源是否立即可用,有则返回 
2.没有,就等...

对于 
1.这个时候不管有没信号,也不返回EINTR,只管执行自己的就可以了 
2.采用睡眠来等待,发生信号的时候进程会被唤醒,socket接口唤醒后检查有无未处理的信号(signal_pending)会返回EINTR错误。

所以 
socket接口并不是被信号中断,只是调用了睡眠,发生信号睡眠会被唤醒通知进程,然后socket接口选择主动退出,这样做可以避免一直阻塞在那里,有退出的机会。非阻塞时不会调用睡眠。

我们看看linux内核里的实现 
linux kernel 3.5.5 

原文地址:https://www.cnblogs.com/alantu2018/p/8446905.html

时间: 2024-11-09 02:07:36

Linux中断的系统调用的相关文章

Linux环境编程之信号(二):不可靠信号、中断的系统调用、可重入函数

(一)不可靠信号 对前面说的信号,是不可靠的,不可靠指的是信号可能会丢失:一个信号发生了,但进程却可能一直不知道这一点.另外,进程对信号的控制能力有限,只能捕捉信号或忽略它.有时用户希望通知内核阻塞一个信号:不要忽略它,在其发生时记住它,然后在进程做好准备时再通知它.这种阻塞信号的能力并不具备. 之前的版本中村咋一个问题:在进程每次接到信号对其进行处理时,随即将该信号动作复位为默认值.另一个问题是,在进程不希望某种信号发生时,它不能关闭该信号.进程能做的一切就是忽略该信号. (二)中断的系统调用

基于int的Linux的经典系统调用实现

 先说明两个概念:中断和系统调用 一 系统调用: 是应用程序(运行库也是应用程序的一部分)与操作系统内核之间的接口,它决定了应用程序是如何和内核打交道的. 1,  Linux系统调用:2.6.19版内核提供了319个系统调用.比如 exit fork read open close …… 2,  对Windows来说,操作系统提供给应用程序的接口不是系统调用,而是API.比如:ReadFile.我们暂时把API和系统调用等同起来 3,  Linux中,每个系统调用对应一个系统调用号,内核维护了一

linux中断子系统:中断号的映射与维护

写在前沿: 好久好久没有静下心来整理一些东西了,开始工作已有一个月,脑子里想整理的东西特别多.记录是一种很好的自我学习方式,静下来多思考多总结,三年的工作目标不能发生变化,作为职场菜鸟即将进入全世界半导体第一的Intel working,是机遇更是一种挑战,困难也是可想而知.脚踏实地.仰望星空,以结果为导向,以目标为准则,争取每天进步一点点. Linux内核版本:3.4.39 一. linux中断子系统的irq_desc初始化 linux内核最初的中断初始化过程入口为start_kernel.在

Linux 中断详解 【转】

转自:http://blog.csdn.net/tiangwan2011/article/details/7891818 原文地址 http://www.yesky.com/20010813/192117.shtml 方法之三:以数据结构为基点,触类旁通 结构化程序设计思想认为:程序 =数据结构 +算法.数据结构体现了整个系统的构架,所以数据结构通常都是代码分析的很好的着手点,对Linux内核分析尤其如此.比如,把进程控制块结构分析清楚 了,就对进程有了基本的把握:再比如,把页目录结构和页表结构

Linux下的系统调用

1.linux的的用户态与内核态 Intel x86架构的CPU有0~3四种执行级别,0级最高,3级最低,  linux只使用0级和3级,分别表示内核态和用户态.linux中,只有内核态能访问逻辑地址为0xc0000000以上的空间.执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态.程序由用户态进入内核态的方式是中断. 系统调用的核心是使用操作系统为用户特别开放的一个中断,例如linux的int 80h中断,系统中断是由用户态

Linux内核(四)系统调用

转载请注明出处:jiq?钦's technical Blog 什么是系统调用? 系统调用--内核和用户应用程序的桥梁,中间人. 系统调用就是内核实现的一系列函数,这些函数提供了一套固定的接口,通过这套接口,用户程序可以访问系统硬件和操作系统的资源,即内核提供的服务. 为什么提供系统调用? 用户空间只能通过系统调用来访问内核提供的服务的根本原因是为了对系统进行"保护",因为我们知道Linux的运行空间分为内核空间与用户空间,它们各自运行在不同的级别中,逻辑上相互隔离.用户进程在通常情况下

中断的系统调用

早期UNIX系统的一个特性是:如果在进程执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断不再继续执行.该系统调用返回出错,其errno设置为EINTR.这样处理的理由是:因为一个信号发生了,进程捕捉到了它,这意味着已经发生了某种事情,所以是个好机会应当唤醒阻塞的系统调用. 在这里,我们必须区分系统调用和函数.当捕捉到某个信号时,被中断的是内核中的执行的系统调用. 为了支持这种特性,将系统调用分成两类:低速系统调用和其他系统调用.低速系统调用是可能会使进程永远阻塞的一类系统调用,

linux中断源码分析 - 初始化(二)

本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 本篇文章主要讲述源码中是如何对中断进行一系列的初始化的. 回顾 在上一篇概述中,介绍了几个对于中断来说非常重要的数据结构,分别是:中断描述符表,中断描述符数组,中断描述符,中断控制器描述符,中断服务例程.可以说这几个结构组成了整个内核中断框架主体,所以内核对整个中断的初始化工作大多集中在了这几个结构上. 在系统中,当一个中断产生时,首先CPU会从中断描述符表中获取相应的中断向量,并根据中断向量的权限位判断是否

向linux内核添加系统调用新老内核比较

2.6内核 1>修改linux-source-2.6.31/kernel/sys.c文件,在文件末尾添加系统响应函数.函数实现如下: asmlinkage int sys_mycall(int number) { printk("这是我添加的第一个系统调用"); return number; } 2>在linux-source-2.6.31/arch/x86/kernel/syscall_table_32.S 中添加:.long sys_mycall 如: .long sy