Linux|Linux进程信号

Linux进程信号

文章目录

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

Linux|Linux进程信号
文章图片

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

Linux|Linux进程信号
文章图片

3.信号的种类
程序崩溃收到的信号
  • 解引用空指针:11号信号,解引用空指针,悬垂指针
  • 内存访问越界:11号信号,操作系统容忍进程访问不属于自己的内存,但前提条件是越界访问的内存没有分配给其他进程使用
  • 除0:8号信号
  • double free:6号信号
4.信号的处理方式
Linux|Linux进程信号
文章图片

1.默认处理方式:SIG_DFL,操作系统中已经定义好处理信号的方式了
比如:2->终止进程,11->终止进程并核心转储文件
2.忽略处理方式:SIG_IGN(僵尸进程)
进程收到忽略处方式的信号后,是不会进行处理的
SIGCHLD信号:子进程先于父进程退出,子进程退出的时候会给父进程发送SIGCHLD,而父进程收到这个信号后,是忽略处理的,导致了父进程并没有回收子进程的退出状态信息,从而子进程变成了僵尸进程
3.自定义处理方式:程序员可以更改信号的处理方式,定义一个函数,当进程收到该信号的时候,调用程序员自己写的函数
5.信号的注册
Linux|Linux进程信号
文章图片

概念:
【Linux|Linux进程信号】一个进程收到一个信号,这个过程称之为注册
信号的注册与注销并不是一个过程,是两个独立的过程
内核中的定义:
Linux|Linux进程信号
文章图片

Linux|Linux进程信号
文章图片

Linux|Linux进程信号
文章图片

操作系统并非当作数组使用,而是当作位图进行使用
Linux|Linux进程信号
文章图片

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

Linux|Linux进程信号
文章图片

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 #includevoid 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; }

Linux|Linux进程信号
文章图片

8.信号的捕捉流程
1.流程图
Linux|Linux进程信号
文章图片

2.信号的处理时机:
当从内核切换回用户态时,会调用do_signal函数处理信号
有,就处理信号
没有则直接返回用户态
3.处理信号的不同处理方式
默认,忽略直接在内核就处理结束。
自定义处理:
执行用户自定义的处理函数(用户空间)
调用sigreturn()再次回到操作系统内核(内核空间)
再次调用会调用do_signal函数处理信号
调用sys_sigreturn函数回到用户空间,继续执行代码
4.常见的进入内核的方式
调用系统调用函数
内存访问越界,访问空指针
调用库函数
9.信号的阻塞
1.信号的注册是信号的注册,信号阻塞是信号阻塞,信号的阻塞并不会干扰信号的注册,而是说进程收到这个信号后,暂时不处理这个信号,而是等信号不阻塞之后再进行注册
3.函数接口:
Linux|Linux进程信号
文章图片

  • 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 #includevoid 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|Linux进程信号
文章图片

Linux|Linux进程信号
文章图片

20s后子进程退出。
Linux|Linux进程信号
文章图片

    推荐阅读