Linux进程信号

Linux进程信号

文章目录

      • 1.信号的概念
      • 2.信号的产生
      • 3.信号的种类
      • 4.信号的处理方式
      • 5.信号的注册
      • 6.信号的注销
      • 7.信号的自定义处理方式
      • 8.信号的捕捉流程
      • 9.信号的阻塞
      • 10.扩展

Linux进程信号_第1张图片

1.信号的概念

信号是进程之间事件异步通知的一种方式,属于软中断。
只是告诉有这样一个信号,但是具体这个信号怎么处理,什么时候处理由进程决定的。所以是软中断

2.信号的产生

硬件产生:

1.ctrl+c:2号信号 SIGINT(键盘当中按下ctrl+c结束一个进程的时候,其实是进程收到了2号信号。2号信号导致了进程的退出。 )
2.ctrl+z:20号信号 SIGTSTP
3.ctrl+|:3号信号 SIGQUIT
4.kill命令向进程发送信号:kill -[信号值] [pid]

软件产生:

  • kill函数:#include int kill(pid_t pid,int sig);
    pid:进程号,给哪个进程发就填这个进程的进程号
    sig:要发送的信号的值
  • raise函数:int raise(int sig);
    谁调用给谁发信号
    sig:要发送的信号值
    该函数的实现实际时调用kill函数
    int raise(int sig){ return kill(getpid(),sig); }
  • kill -[num] [pid] 可以给进程发信号
int main(){
    raise(2);
    printf("If you saw that, you'd be damned\n");
    kill(getpid(), 3);
    while(1){
        printf("i am siganl test process...\n");
        sleep(1);
    }
    return 0;
}

在这里插入图片描述

3.信号的种类

程序崩溃收到的信号

  • 解引用空指针:11号信号,解引用空指针,悬垂指针
  • 内存访问越界:11号信号,操作系统容忍进程访问不属于自己的内存,但前提条件是越界访问的内存没有分配给其他进程使用
  • 除0:8号信号
  • double free:6号信号

4.信号的处理方式

Linux进程信号_第2张图片

1.默认处理方式:SIG_DFL,操作系统中已经定义好处理信号的方式了

比如:2->终止进程,11->终止进程并核心转储文件

2.忽略处理方式:SIG_IGN(僵尸进程)

进程收到忽略处方式的信号后,是不会进行处理的
SIGCHLD信号:子进程先于父进程退出,子进程退出的时候会给父进程发送SIGCHLD,而父进程收到这个信号后,是忽略处理的,导致了父进程并没有回收子进程的退出状态信息,从而子进程变成了僵尸进程

3.自定义处理方式:程序员可以更改信号的处理方式,定义一个函数,当进程收到该信号的时候,调用程序员自己写的函数

5.信号的注册

Linux进程信号_第3张图片

概念:

一个进程收到一个信号,这个过程称之为注册
信号的注册与注销并不是一个过程,是两个独立的过程

内核中的定义:
Linux进程信号_第4张图片
Linux进程信号_第5张图片
Linux进程信号_第6张图片
操作系统并非当作数组使用,而是当作位图进行使用
Linux进程信号_第7张图片
signal数组当中一个元素有64个比特位

注册过程:

位图更改为1,添加sigqueue节点到sigqueue队列
信号注册的时候会将对应信号的比特位从0置为1,表示在当前进程受到了该信号
还需要在sigaqueue队列中添加一个sigqueue节点,队列在操作系统内核中本质就是一个双向链表(先进先出)

区别:

  • 非实时信号的注册
    第一次注册:修改sig位图(0-1),修改sigaueue队列
    第二次注册相同信号值的信号:修改sig位图(1-1),并不会添加siaqueue节点
  • 实时信号的注册
    第一次注册:修改sig位图(0-1),修改sigqueue队列
    第二次注册相同信号值的信号:修改sig位图(1-1),添加siaqueue节点到sigqueue队列当中

6.信号的注销

  • 非可靠信号:
    1.将信号对应的sig位图中的比特位(1-0)
    2.将对应的信号sigqueue节点进行出队操作
  • 可靠信号:
    1.将对应的信号sigqueue节点进行出队操作
    2.判断sigqueue队列当中还有相同信号的sigqueue节点吗如果有:则比特位不变,如果没有:则比特位改变位0

7.信号的自定义处理方式

1.第一种signal

  • 函数:sighandler_t signal(int signum,sighadler_t handler);
    signum:信号值
    handler:更改为一个函数处理,接收一个函数的地址,函数指针
    typedef void (*sighandler_t)(int) ;无返回值,参数为int
  • 注意:在调用signal函数的时候,给第二个参数传递函数地址的时候并没有调用传递的函数。而是,等到进程收到了某个信号之后,才回调刚刚注册的函数
    9号信号(强杀〉就是不能被程序员自定义处理的信号。

代码:

#include 
#include 
#include 

void sigcallback(int sig){
    //sig : 触发调用该函数, 收到的信号值
    printf("recv signal num is %d\n", sig);
}

int main(){
    //1.自定义2号信号的处理方式
    signal(2, sigcallback);
    signal(3, sigcallback);
    signal(9, sigcallback);

    while(1){
        //printf("test signal process...\n");
        sleep(1);
    }
    return 0;
}

在这里插入图片描述
2.第二种sigaction

函数:int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);
signum:信号值
act:将信号的处理方式更改为act
oldact:原来信号的处理方式

struct sigaction{
	void (*sa_handler)(int);//保存信号处理方式的函数指针
	void (*sa_sigaction)(int,siginfo_t *,void *);//也是保存信号的处理方式的函数指针,但是没有使用,使用的时候配合sa_flags一起使用,当sa_flags的值为SA_SIGIFC的时候,信号按照sa_sigaction保存的函数地址进行处理
	sigset_t sa_mask;//当进程处理信号的时候,如果还有收到信号,则放到该信号位图中,后续再放到进程的信号位图中
	int sa_flags;
	void (*sa_restorer)(void);//保留字段
};
#include
#include
#include

void sigcallback(int sig){
  printf("rcv signalnum is %d\n",sig);

}
int main()
{
  //自定义二号函数处理方式sigaction
  //定义一个结构体对象
  struct sigaction act;
  act.sa_handler=sigcallback;
  sigemptyset(&act.sa_mask);

  struct sigaction oldact;
  sigaction(2,&act,&oldact);

  while(1){
    sleep(1);
  }
  return 0;
}

在这里插入图片描述

8.信号的捕捉流程

1.流程图
Linux进程信号_第8张图片
2.信号的处理时机:

当从内核切换回用户态时,会调用do_signal函数处理信号
有,就处理信号
没有则直接返回用户态

3.处理信号的不同处理方式

默认,忽略直接在内核就处理结束。
自定义处理:
执行用户自定义的处理函数(用户空间)
调用sigreturn()再次回到操作系统内核(内核空间)
再次调用会调用do_signal函数处理信号
调用sys_sigreturn函数回到用户空间,继续执行代码

4.常见的进入内核的方式

调用系统调用函数
内存访问越界,访问空指针
调用库函数

9.信号的阻塞

1.信号的注册是信号的注册,信号阻塞是信号阻塞,信号的阻塞并不会干扰信号的注册,而是说进程收到这个信号后,暂时不处理这个信号,而是等信号不阻塞之后再进行注册

3.函数接口:

在这里插入图片描述

  • how:想让ssigprocmask做什么事情
    SIG_BLOCK:设置某个信号的阻塞状态,计算新的阻塞位图方式为:block(new)=block(old)|set
    SIG_UNBLACK:设置某个信号为非阻塞状态,计算新的阻塞位图方式为:block(new)=block(old)&(~set)
    SIG_SETMASK:用第二个参数替换原来的阻塞位图,计算新的阻塞位图方式为:block(new)=set
    set:新设置的阻塞位图
    oldset:原来的老的阻塞位图

4.代码:

int main(){

    //1.将40号/2号信号自定义信号的处理方式
    signal(40, sigcallback);
    signal(2,  sigcallback);

    //2. 阻塞"全部"信号(40/2)
    sigset_t set;
    //将 set 的所有比特位全部设置为1 : int sigfillset(sigset_t *set);
    sigfillset(&set);

    sigprocmask(SIG_BLOCK, &set, NULL);
    	return 0}

10.扩展

前面提到:子进程先于父进程退出,子进程退出的时候会给父进程发送SIGCHLD,而父进程收到这个信号后,是忽略处理的,导致了父进程并没有回收子进程的退出状态信息,从而子进程变成了僵尸进程。
之前调用wait/wait_pid解决,但是前者会导致父进程阻塞,无法运行,后者虽然设置了非阻塞,但是需要循环,子进程不退出之前,父进程只有循环体内的代码可以运行,那么该如何完美解决呢?

父子进程+进程等待+自定义信号处理方式

#include
#include
#include
#include
#include

void sigcallback(int sig){
  //打印这句话说明子进程退出
  printf("recv sig num is %d\n",sig);
  
  //非阻塞,上面子进程已经退出了
  int status;
  wait(&status);

}

int main(){
  pid_t pid=fork();
  if(pid<0){
    perror("fork");
    return 0;
  }else if(pid==0){
    //child
    sleep(5);
    exit(1);
  }else{
    //father
    //不需要阻塞,也不需要循环
    signal(SIGCHLD,sigcallback);

    while(1)
    {
      printf("i am father process\n");
    }
  }
  return 0;
}

Linux进程信号_第9张图片

Linux进程信号_第10张图片
20s后子进程退出。
在这里插入图片描述

你可能感兴趣的:(Linux,c++,linux)