信号

 信号实质是软中断。

 信号作用:通知事件的发生,信号产生之后第一时间也不是直接处理,而是先存储下来,再处理信号。

 信号的产生 ---> 信号的注册 ---> 信号的阻塞 ---> 信号的注销 ---> 信号的处理

在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;
}

 进程信号

  • 软件条件产生: 

           如: int kill (pid_t pid, int sig);    // 向指定进程发送指定信号

                   int raise (int sig);   // 向自身发送信号

                   unsigned int alarm (unsigned int n);   // 定时器,在n秒后向进程发送SIGALARM信号,在设置一个定时器,会取消上一个定时器,并返回上一个定时器剩余时间。

                   函数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,以说明此信号不处理。

  • 信号的递达即处理信号
  • 信号未决:是一种状态,信号从注册成功到信号的递达之间的一种状态。
int  sigemptyset (sigset_t *set);   // 	清空一个信号集合
int  sigfillset(sigset_t *set);   	//  将所有信号都添加到set集合
int  sigaddset(sigset_t *set,int signum);    // 添加指定的单个信号到set集合中
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);   // 阻塞信号/解除阻塞

说明:

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

signum:指定信号

how:

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

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

扩充:

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

         sigpending 获取未决信号

  • 注销

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

可靠信号:

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

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

非可靠信号:

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

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

  • 处理
sighandler_t signal (int  signum,   sighandler_t  handler);

说明:

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

修改信号处理方式:

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

       信号是当我们发起系统调用/程序异常/中断当前程序从用户态运行切换到内核态,去处理这些事情,处理完毕之后,要

从内核态返回用户态,但是返回之前会看(在pending位图)一下是否有信号需要被处理,如果有,就处理信号(切换到用

户态执行信号的自定义处理方式),处理完毕之后再次返回到内核态,判如果没有信号要处理了就调用sys_sigreturn返回

用户态(我们程序之前的运行位置)。

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

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

 

关于僵尸进程的避免:

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

    信号:SIGCHLD - 17号信号

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

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

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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

相关文章:

  • 2022-12-23
  • 2021-07-28
  • 2021-10-10
  • 2021-10-28
  • 2022-12-23
  • 2022-01-15
  • 2022-12-23
  • 2021-06-15
猜你喜欢
  • 2021-06-15
  • 2022-12-23
  • 2021-08-15
  • 2021-05-31
  • 2022-01-20
相关资源
相似解决方案