第8章 信号(4)_中断的系统调用和函数可重入性

3.2 中断的系统调用

(1)进程调用“慢”系统调用时如果发生了信号,内核会重启系统调用(即“慢”系统调用被信号中断后,当信号处理完毕后,该系统调用会被重新从头开始执行,而不是从中断的地方继续执行!注意,这是重启的方式。除此之处,还有设置SA_RESTART和忽略信号等方式)

(2)慢系统调用

  ①可能会永远阻塞的系统调用

  ②从终端设备、管道或网络设备上的文件读取。

  ③向上述文件写入

  ④某些设备上的文件打开

  ⑤pause和wait系统调用

  ⑥一些设备的ioctl操作

  ⑦一些进程间的通信函数

(3)如何处理被中断的系统调用

  ①重启被中断的系统调用:如accept、read、write、select、wait、waitpid和open等。(是重启系统调用还是让系统调用失败?在早期的Unix是让系统调用失败,并返回-1,同时设置errno,如pause被中断唤醒而不是重启)

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

  ③忽略信号让系统不产生信号中断。

【编程实验】中断系统调用

//signal_syscall.c

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

void sig_handler(int signo)
{
    //ctrl-z信号
    if(signo == SIGTSTP){
        printf("SIGTSTP occured
");
    }
}

int main(void)
{
    char buffer[512];
    ssize_t size;

    //注册信号处理函数
    if(signal(SIGTSTP, sig_handler) == SIG_ERR){
        perror("signal sigtstp error");
    }

    printf("begin running and waiting for signal
");
    
    //由于read会阻塞等待输入,如果此时先输入一些内容,然后按ctrl-z
    //这里由于read被信号中断,会重启read函数,表现出来的是重新生成
    //提示符等待用户输入,之前的输入全部清空。
    size = read(STDIN_FILENO, buffer, sizeof(buffer)); //慢系统调用
    if(size < 0){
        perror("read error");
    }
    printf("reading finished
");

    if(write(STDOUT_FILENO, buffer, size) != size){ //慢系统调用
        perror("write error");
    }

    printf("end running
");

    return 0;
}
/*输出结果:
 [root@localhost]# bin/signal_syscall
 begin running and waiting for signal
 abcd                  //输完内容后,按回车正常结束一行的输入
 reading finished
 abcd
 end running
 [root@localhost]# bin/signal_syscall
 begin running and waiting for signal
 abcaksdfj ^ZSIGTSTP occured //输入一些内容后,按ctrl-z产生信号,中断read
 mnk                     //这里会重启read,原内容被清空,重新生成提示符
 reading finished
 mnk
 end running
 */

【编程实验】中断用户自定义的函数

//signal_usercall.c

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

void sig_handler(int signo)
{
    if(signo == SIGTSTP){
        printf("SIGTSTP occured
");
    }
}

//当用户函数被信号中断后,信号处理完之后,会从中断的地方
//继续执行,而不会重启整个函数。这是与慢系统调用不同的。
void call_fun(void)
{
    printf("begin running call_fun
");

    sleep(5); //演示在这期间产生信号中断现象
    
    printf("end running call_fun
");
}

int main(void)
{
    if(signal(SIGTSTP, sig_handler) == SIG_ERR){
        perror("signal sigtstp error");
    }

    printf("begin running main
");
    call_fun();
    printf("end running main
");
    return 0;
}
/*
 [root@localhost]# bin/signal_usercall
 begin running main    //正常流程
 begin running call_fun
 end running call_fun
 end running main
 [root@localhost]# bin/signal_usercall
 begin running main     //在call_fun执行过程中按ctrl-z中断
 begin running call_fun
 ^ZSIGTSTP occured      //发生信号,当信号处理完毕后,从中
 end running call_fun   //断的地方开始继续执行,而不是重启整个函数
 end running main
 */

3.3 函数的可重入性

(1)在调用某个函数过程中出现信号且该信号处理函数中再次调用该函数,就可能产生可重放入性问题。

(2)访问全局或静态变量的函数是不可重入函数

(3)程序片断

//可重入函数
int double(int a){
    return a * 2;
}

//不可重入函数
void foo(){
    static int array[28]={0};
    static int index = 0;
    
    if(index > 19) return;
    array[index] = 9;
    index++;
}

【编程实验】函数的可重入性

//signal_reentry.c

#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

int  g_v[10]; //全局数组
int* h_v;     //堆数组

void set(int val)
{
    int a_v[10]; //局部数组

    int i = 0;
    for(; i<10; i++){
        a_v[i] = val;
        g_v[i] = val;
        h_v[i] = val;

        sleep(1);
    }

    printf("g_v:");
    for(i=0; i<10; i++){
        if(i != 0)
            printf(",%d", g_v[i]);
        else
            printf("%d", g_v[i]);
    }
    printf("
");
    
    printf("h_v:");
    for(i=0; i<10; i++){
        if(i != 0)
            printf(",%d", h_v[i]);
        else
            printf("%d", h_v[i]);
    }
    printf("
");

    printf("a_v:");
    for(i=0; i<10; i++){
        if(i != 0)
            printf(",%d", a_v[i]);
        else
            printf("%d", a_v[i]);
    }
    printf("
");
}

//信号处理函数
void sig_handler(int signo)
{
    if(signo == SIGTSTP){
        printf("SIGTSTP occured
");
        set(20); //信号处理函数内部再次调用set,以验证函数的
                 //可重入性。注意,传入20
        printf("end SIGTSTP
");
    }
}

int main(void)
{
    if(signal(SIGTSTP, sig_handler) == SIG_ERR){
        perror("signal sigtstp error");
    }

    h_v = (int*)calloc(10, sizeof(int));
    
    printf("begin running main
");
    set(10);  //传入10
    printf("end running main
");

    free(h_v);
}
/*
 [root@localhost]# bin/signal_reentry                             
 begin running main  //正常执行,不会触发信号中断
 g_v:10,10,10,10,10,10,10,10,10,10
 h_v:10,10,10,10,10,10,10,10,10,10
 a_v:10,10,10,10,10,10,10,10,10,10
 end running main
 [root@localhost]# bin/signal_reentry                             
 begin running main   //运行2-3秒后,按ctrl-z触发信号中断
 ^ZSIGTSTP occured    //在信号处理函数中调用set赋值
 g_v:20,20,20,20,20,20,20,20,20,20
 h_v:20,20,20,20,20,20,20,20,20,20
 a_v:20,20,20,20,20,20,20,20,20,20
 end SIGTSTP   //信号处理完,由原中断处继续执行,这里从第4个元素继续赋值(10)
 g_v:20,20,20,10,10,10,10,10,10,10
 h_v:20,20,20,10,10,10,10,10,10,10
 a_v:10,10,10,10,10,10,10,10,10,10
 end running main
 */

3.4 信号的特点

(1)信号的发生是随机的,但信号在何种条件下发生是可预测的(如按ctrl-z将发送SIGTSTP信号)。

(2)进程刚开始启动时所有信号的处理方式要么默认,要么忽略,忽略的是SIGUSR1和SIGUSR2两个信号,其它都采取默认的方式(大多数是终止进程)

(3)进程在调用exec函数后,原有信号的捕捉函数失效

(4)子进程的诞生总是继承父进程的信号处理方式

(5)在系统层面上,信号的发生是可靠的在linux中的可靠性只保证一次,进程在处理信号期间若发生同类型的信号不会丢失(内核会保留),但会被延迟处理,但同类型信号的多次发生只会保留一次(即被处理一次)。若不同类型的信号发生也会被内核保留直接被处理,处理完后再处理原有信号

(6)用户层面可靠性依赖于信号而执行的用户代码放置在信号处理程序内部执行,否则不一定可靠。

(7)在信号发生时,慢系统调用可以被中断并在信号处理后系统调用会被重启

(8)在信号发生时,用户函数可以被中断但不能被重启,沿着中断点继续执行(在用户函数中要保证数据一致性,即可重入性不要去访问全局变量和静态变量,堆中的变量若在用户函数内部分配没有关系,否则会出现不可重入性)

原文地址:https://www.cnblogs.com/5iedu/p/6402506.html