进程信号

本文围绕Linux信号展开,介绍信号实质是软中断,用于通知进程事件。阐述其生命周期,包括产生、注册、阻塞、处理和注销。说明了信号分类,介绍多种产生方式及相关函数。还讲解信号阻塞、处理、捕捉原理,以及可靠与非可靠信号注销区别,最后提及进程信号与信号量差异及避免僵尸进程方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

    信号

        信号产生

        信号注册

        信号阻塞(屏蔽)

        信号处理

        信号注销


信号

  •  信号实质是软中断,用于通知进程发生某些事件。
  •  信号作用:通知事件的发生,信号产生之后第一时间也不是直接处理,而是先存储下来,再处理信号。
  •  信号生命周期: 信号的产生 ---> 信号的注册 ---> 信号的阻塞 ---> 信号的注销 ---> 信号的处理

在Linux下,有62种信号:

信号分了两类:

  1.  1~31不可靠信号(非实时信号)
  2.  34~64 可靠信号(实时信号)

 

  •  信号产生

  • 通过硬件中断; //  SIGINT(2号信号,Ctrl + C产生)、SIGQUIT(3号信号,Ctrl + \产生)和SIGTSTP(20号信号,Ctrl+Z产生)  

测试代码:

#include <iostream>                                                                                                                                            
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

using namespace std;

int main(){
    pid_t pid = fork();
    if(pid < 0){ 
        cerr << "fork error" << endl;
        exit(0);
    }   
    else if(pid == 0){ 
        while(1){
            sleep(1);
            cout << "The Child Pid = " << getpid() << ", PPid = " << getppid() << endl;
        }   
    }   
    else{
        int status;
        wait(&status);
        cout << "exit num is = " << status << endl;
    }   

    return 0;
}

在程序执行时,通过ctrl+[c \ z]的结果:

上面结果并未返回子进程退出信息,这是因为按键操作使得父进程也退出,且父进程没机会能打印出子进程退出信息。可以通过给子进程发送退出信号的方式来得到希望结果(在后续展示。。。)。

  • 程序异常产生; // 比如当前进程访问了非法地址空间,MMU(内存管理单元)会产生异常,内核将这个异常解释为SIGSEGV信号(11号信号)发送给进程、若程序中有除0错误,也会给进程发送一个信号(SIGFPE -- 8号信号)。 

测试代码:

int main(){
    pid_t pid = fork();
    if(pid < 0){ 
        cerr << "fork error" << endl;
        exit(0);
    }   
    else if(pid == 0){ 
        int n = 0;
        n = 10/0;
    }   
    else{
        int status;
        wait(&status);
        cout << "exit num is = " << status << endl;
    }   

    return 0;
}

 

  • 软件条件产生: 
  1.  int kill (pid_t pid, int sig);    // 向指定进程发送指定信号
  2.  int sigqueue(pid_t pid, int sig, const union sigval value); // 给指定进程发送指定信号,同时可以携带一个参数
  3.  int raise (int sig);   // 向自身发送信号
  4.  unsigned int alarm (unsigned int n);   // 定时器,在n秒后向进程发送SIGALARM信号,在设置一个定时器,会取消上一个定时器,并返回上一个定时器剩余时间。
  5.  函数abort,是为了让进程异常终止的。
#include <stdlib.h>

void abort (void);

测试代码:

int main(){
    pid_t pid = fork();
    if(pid < 0){
        cerr << "fork error" << endl;
        exit(0);
    }
    else if(pid == 0){
        int count = 3;
        while(count--){
            sleep(1);
            cout << "The Child Pid = " << getpid() << ", PPid = " << getppid() << endl;
        }
        raise(3); // 向自身发送3信号
        abort();
    }
    else{
        int status;
        kill(pid, 9); // 向子进程发送9号信号
        wait(&status);
        cout << "exit num is = " << status << endl;
    }   

    return 0;
}    

调用kill函数:

调用raise()函数: 

 

调用abort():

 

  • 信号注册

  • 信号的注册即给一个进程发送信号,就是修改这个进程pcb中关于信号的pending位图,将相应的信号位置1;
  • 信号的阻塞:暂时不处理信号即阻止信号的递达,并不是不接收信号;

               原理: 要阻塞一个信号那么就是将 pcb 中关于信号的block位图再修改,将相应信号位置1,以说明此信号不处理。

  • 信号的递达即处理信号
  • 信号未决:是一种状态,信号从注册成功到信号的递达之间的一种状态。

 

  • 信号阻塞(屏蔽)

其他概念:

  • 实际执行信号的处理动作称为信号递达(Delivery);
  • 信号从产生到递达之间的状态,称为信号未决(Pending);
  • 进程可以选择阻塞(Block)某个信号;
  • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作;
  • 阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是递达之后可选的一种动作。

 

说明:

       在pcb中有一个pending位图,其中存储当前接受到的信号,还有一个blocked位图用于存储现在都有哪些信号要被阻塞,它们之间不会相互影响。进程看到pending集合中收到那些信号,在处理这些信号之前,进程先查看一下blocked集合是否存有,如果存在了,就意味着这个信号现在将不被处理,直到解除阻塞

#include <signal.h>

// 清空一个信号集合
   int  sigemptyset (sigset_t *set);   

// 将所有信号都添加到set集合
   int  sigfillset(sigset_t *set);   	

// 当前pending集合(信号注册集合)中的信号取出放到set中
   int sigpending(sigset_t *set);

// 添加指定的单个信号到set集合中
   int  sigaddset(sigset_t *set,int signum);    

// 从集合中移除一个指定的信号
   int  sigdelset(sigset_t *set, int signum);	

// 判断一个信号是否在一个集合中
   int  sigismember(const sigset_t *set,int  signum);  

// 阻塞信号/解除阻塞
   int  sigprocmask(int how, sigset_t *set, sigset_t *oldset);   

说明:

sigset_t:用相同的数据类型sigset_t 来存储未决和阻塞标志,sigset_t称为信号集。阻塞信号集也叫做信号屏蔽字(Signal Mask)。

set: 要阻塞/解除阻塞的集合

signum:指定信号

how

  • SIG_BLOCK       阻塞集合中的信号
  • SIG_UNBOCK    对集合中的信号解除阻塞
  • SIG_SETMASK  将集合中的信号设置到阻塞集合中

oldset: 保存原先阻塞集合中的信号

扩充:

         有两个信号不会被阻塞:SIGKILL 和 SIGSTOP

         sigpending 获取未决信号

 

 

  • 信号处理

sighandler_t signal (int  signum,   sighandler_t  handler);

说明:

  • signum:信号的编号
  • handler:处理方式:1. SIG_IGN 忽略   2. SIG_DFL 默认

修改信号处理方式:

  • 默认处理:按照操作系统中对信号事件的既定处理方式
  • 忽略处理:直接将信号丢掉。
  • 自定义处理:用户自定义事件的处理方式。

 

       信号是当我们发起系统调用/程序异常/中断当前程序从用户态运行切换到内核态,去处理这些事情,处理完毕之后,要从内核态返回用户态,但是返回之前会看(在pending位图)一下是否有信号需要被处理,如果有,就处理信号(切换到用户态执行信号的自定义处理方式),处理完毕之后再次返回到内核态,判如果没有信号要处理了就调用sys_sigreturn返回用户态(我们程序之前的运行位置)。

 

信号捕捉:

       对于默认和忽略的处理方式可以在内核态直接处理,而对于是自定义处理方式时,信号递达需要先调用自定义函数处理,这一过程叫信号捕捉

       原理:

      捕捉时机:

内核态切换到用户态。

内核态:操作系统本身的状态;

用户态:受限状态,必须在操作系统搭建好的状态下运行。

 

     实现方式:

         sigaction接口(较为多用):自定义信号的处理方式,并且signal函数内部也是通过sigaction实现的。

#include <signal.h>

int sigcation(int  signum, const struct sigaction *act, struct sigaction *oldact);
//  signum:信号编码
//  act:新的处理方式
//  oldacr:保存原因处理方式

 

struct sigaction{
  void	(*sa_handler) (int);   	                      //处理函数
  void (*sa_sigaction) (int, siginfo_ *, void *);     // 处理函数
  sigset_t  sa_mask;                                  //在处理信号的时候可以 通过这个mask 暂时阻塞一些信号,处理完毕之后会还原回去 
  int  sa_flags;	                              //决定了我们使用那个回调接口,并且换有其他选项信息
  void  (*sa_restorer) (void);
}

 

  • 信号注销

注销就是从pending 集合中将即将要处理的信号相应位置0。

可靠信号:

       对于可靠信号就是不管当前信号是否注册都要置1,且添加到链表中,所以不会弄丢信号。

       注销就是删除节点,且判断是否有相同信号节点,若没有则位图置0,否则就不置0.

非可靠信号:

       对应非可靠信号注册就是在pending位图相应位置1,然后添加一个sigqueue结构到链表中,如果之后有相同信号到来,而相应位是1,则不做操作,也就不会重复注册,相当于丢了。

       注销就是删除链表节点,且相应位置0.

 

扩展补充:

  • 进程信号与信号量:

信号: 实质是软中断,信号也算作是进程通信的一种方式;

信号量:实际上是等待队列的计数器,它表示是否有可用临界资源。

  • 关于僵尸进程的避免:

操作系统如何通知父进程,子进程退出呢?

    信号:SIGCHLD - 17号信号

    在没有学习信号,为避免僵尸进程,只能让父进程一直等待子进程的退出(因为实在是不知道子进程到底什么时候退出),浪费了父进程资源。现在可以自定义SIGCHILD的处理方式,相当于提前告诉进程,当接收到这个信号时使用waitpid,这样就不用一直等了。

    使用非阻塞的循环来处理SIGCHILD信号

     因为SIGCHILD信号是不可靠信号,就有可能丢失,因此就有可能漏掉僵尸子进程没有处理,所以一旦收到信号就处理到不能处理为止。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值