Linux信号处理机制基础

什么是信号

  • 信号在最早的UNIX系统中即被引入,已有30多年的历史,但只有很小的变化。
  • 信号是提供异步事件处理机制的软件中断
  • 进程之间可以相互发送信号,这使信号成为一种进程间通信(Inter-ProcessCommunication,lPC)的基本手段

信号的名称与编号

  • 信号是很短的消息,本质就是一个整数,用以区分代表不同事件的不同信号。为了便于记忆,在signum.h头文件中用一组名字前缀为SIG的宏来标识信号,即为信号的名字。
  • 通过kill -l命令可以查看信号
  • 一共有62个信号,其中前31个信号为不可靠的非实时信号后31个为可靠的实时信号

 常用信号

信号处理 

  • 忽略:什么也不做,SIGKILL(9)和SIGSTOP(19)不能被忽略
  • 默认:在没有人为设置的情况,系统缺省的处理行为。
  • 捕获:接收到信号的进程会暂停执行,转而执行一段事先编写好的处理代码,执行完毕后再从暂停执行的地方继续运行,

信号处理函数 

signal函数

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

功能:设置调用进程针对特定信号的处理方式

参数:signum         信号编号
        handler 信号的处理方式,可以如下取值
                SIG_IGN        -忽略
                SIG_DFL       - 默认
                信号处理函数指针        -捕获 
返回值:成功返回原信号处理方式,如果之前未处理过则返回NULL,失败返回SIG_ERR

 代码演示

//信号处理
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
//信号处理函数
void sigfun(int signum){
    printf("%d进程:捕获到%d号信号\n",getpid(),signum);
    }
int main(){
    // 对2号信号进行忽略处理
    //接下来程序就有了忽略2号信号的能力
    if(signal(SIGINT,SIG_IGN) == SIG_ERR){
        perror("signal");
        return -1;
    }
    //对2号信号进行捕捉处理
    if(signal(SIGINT,sigfun) == SIG_ERR){
        perror("signal");
        return -1;
    }
    // if(signal(SIGINT,SIG_DFL) == SIG_ERR){
    //     perror("signal");
    //     return -1;
    // }
    for(;;);
    return 0;
}

 信号处理流程

        当有信号到来时,内核会保存当前进程的栈帧,然后再执行信号处理函数。当信号处理函数结束后,内核会恢复之前保存的进程的栈帧,使之继续执行

太平间信号

        无论一个进程是正常终止还是异常终止,都会通过系统内核向其父进程发送SIGCHLD(17)信号。父进程完全可以在针对SIGCHLD(17)信号的信号处理函数中,异步地回收子进程的僵尸,简洁而又高效

        在信号处理函数执行期间,如果有多个相同的信号到来,则只保留一个,其余统统丢弃。如果我们在一次信号处理函数执行期间只进行一次收尸,就会导致漏网的僵尸。那我们又该如何对这些僵尸进程进行回收,我们可以在一次信号处理函数期间尽可能多的回收僵尸进程。此外为防止长时间等待回收子进程影响父进程的执行,我们可以采用非阻塞方式回收僵尸进程。

代码演示 

//太平间信号
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<sys/wait.h>
#include<errno.h>

//信号捕获处理函数
void sigchild(int signum){
    printf("%d进程:捕获到%d号信号\n",getpid(),signum);
    sleep(2);//假装信号处理函数很耗时
    for(;;){
        pid_t pid = waitpid(-1,NULL,WNOHANG);
        if(pid == -1){
            if(errno == ECHILD){
                printf("%d进程:没有子进程了\n",getpid());
                break;
            }else{
                perror("waitpid");
                return ;
            }
        }else if(pid == 0){
            printf("子进程正在运行,回收不了\n");   
            break;
        }else{
            printf("%d进程:回收了%d进程的僵尸\n",getpid(),pid);
        }
    }
}
    /*for(;;){
        pid_t pid = wait(NULL);
        if(pid == -1){
            if(errno == ECHILD){
                printf("%d进程:没有子进程了\n",getpid());
                break;
            }else{
                perror("wait");
                return ;
            }
        }
        printf("%d进程:回收了%d进程的僵尸\n",getpid(),pid);
    }
}*/
int main(){
    if(signal(SIGCHLD,sigchild) == SIG_ERR){
        perror("signal");
        return -1;
    }
    //创建多个子进程
    for(int i = 0; i < 5;i++){
        pid_t pid = fork();
        if(pid == -1){
            perror("fork");
            return -1;
        }
        if(pid == 0){
            printf("%d进程:我是子进程\n",getpid());
            //sleep(1 + i);
            sleep(1);
            return 0;
        }
    }
    //创建老六
    pid_t oldsix = fork();
    if(oldsix == -1){
        perror("fork");
        return -1;
    }
    if(oldsix == 0){
        printf("%d进程:我是老六\n",getpid());
        sleep(15);
        return 0;
    }
    //父进程代码
    for(;;);
    return 0;
}

信号的继承与恢复

  • fork函数创建的子进程会继承父进程的信号处理方式
    • 父进程中对某个信号进行捕获,则子进程中对该信号依然捕获
    • 父进程中对某个信号进行忽略,则子进程中对该信号依然忽略

代码演示 

//验证子进程是否继承父进程的信号处理方式
#include <stdio.h>
#include<unistd.h>
#include<signal.h>
#include<sys/wait.h>

void sigfun(int signum){
    printf("%d进程:捕获到%d号信号\n",getpid(),signum);
}
int main(){
    //忽略2号信号
    if(signal(SIGINT,SIG_IGN) == SIG_ERR){
        perror("signal");
        return -1;
    }
    //捕获3号信号
    if(signal(SIGQUIT,sigfun) == SIG_ERR){
        perror("signal");
        return -1;
    }
    //创建子进程
    pid_t pid = fork();
    if(pid == -1){
        perror("fork");
        return -1;
    }
    //子进程暂时不结束
    if(pid == 0){
        printf("%d进程:我是子进程\n",getpid());
        while(1){
            
        }
        return 0;
    }
        
    //父进程
    printf("%d进程:我是父进程\n",getpid());
    sleep(1);
    return 0;
}

 由于父子进程共用终端设备我们需要在vi上进行,开启两个窗口,向子进程发送2号和3号信号。

窗口1 
day06$./fork
18338进程:我是父进程
18339进程:我是子进程
day06$kill -2 18317
day06$
day06$kill -3 18317
day06$kill -3 18317
day06$kill -3 18317

窗口2
day06$./fork
18316进程:我是父进程
18317进程:我是子进程
day06$18317进程:捕获到3号信号
18317进程:捕获到3号信号
18317进程:捕获到3号信号
  • exec家族函数创建的新进程对信号的处理方式和原进程稍有不同
    • 原进程中被忽略的信号,在新进程中依然被忽略
    • 原进程中被捕获的信号,在新进程中被默认处理

 代码演示

execl.c

//新进程是否继承旧进程的信号处理方式
#include <stdio.h>
#include<unistd.h>
#include<signal.h>
//信号处理函数
void sigfun(int signum){
    printf("%d进程:捕获到%d号信号\n",getpid(),signum);
    return 0;
}
int main(){
    //忽略SIGINT信号
    if(signal(SIGINT,SIG_IGN) == SIG_ERR){
        perror("signal");
        return -1;
    }
    //捕获SIGQUIT信号
    if(signal(SIGQUIT,sigfun) == SIG_ERR){
        perror("signal");
        return -1;
    }
    //变身
    if(execl("./new","new",NULL) == -1){
        perror("execl");
        return -1;
    }

    return 0;
}

new.c

//变身目标
#include<stdio.h>
int main(){
    while(1){}
    return 0;
}
day06$./execl
^C^C^C^C^C^C^C^C^C^\退出 (核心已转储)
day06$

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值