进程信号篇

目录

1.信号的概念:信号是一个软中断

2.信号的种类

3.信号的产生:

4.信号的处理方式

5.信号的注册

6.信号的注销:

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

8.信号的捕捉流程

9.信号的阻塞.

10.扩展


1.信号的概念:信号是一个软中断

  • 问题引入:
    • 信号灯
      • 看到绿灯,可以不走吗?可以走,也可以不走。
      • 看到红灯,可以走吗?可以走,也可以不走。
      • 信号灯只是给我传递了一个消息,走不走,取决于我们自己。(软性行为)
  • 1.1 只是告诉有这样一个信号,但是具体这个信号怎么处理,什么时候处理由进程决定的。所以是软中断

2.信号的种类

  • kill -l 命令可以罗列信号
  • 信号的种类
    • 非实时信号(非可靠信号):
      • 特点:信号可能会丢失
      • 1~31
    • 实时信号(可靠信号):
      • 特点:信号不会丢失
      • 33~ 64
    • 总共定义了62个信号(没有32和33)

3.信号的产生:

  • 硬件产生: .
    • ctrl+c : 2号信号 SIGINT (signal interrupt)
      • 键盘当中按下ctr1+c结束一个进程的时候,其实是进程收到了2号信号。2号信号导致了进程的退出。
    • ctrl+z : 20号信号 SIGTSTP
      • 在Windows操作系统下 ,按下ctrl+z,让进程收到EOF,让进程退出。
    • ctr1+| : 3号信号SIGQUIT
    • 也可以通过命令 kill -[信号值] [pid]
    • kill命令:向进程发送信号
  • 软件产生:
    • kill函数
      • 头文件 :#include<signal. h>
      • int kill(pid_ t pid, int sig);
      • 参数:
        • pid :进程号, 要给那个进程发送信号,则填写那个进程的进程号
        • sig :要发送信号的值
      • #include <stdio.h>
        #include <unistd.h>
        #include <signal.h>
        int main(){
        while(1){
        kill(getpid(),2);
        printf("signal process...\n");
        sleep(1);
        }
        return 0;
        }

    • raise函数
      • int raise(int sig);
      • 谁调用给谁发送信号。
      • sig :要发送的信号值
      • 成功时返回0,失败时返回非零。
      • 该函数的实现当中是调用kill函数
  • 扩展:
    • 崩溃程序收到的信号(结合gdb)
      • gdb调试coredump文件来验证,进程在崩溃的时候收到的是什么信号
      • coredump文件,修改corefile size(一般情况下不需要考虑磁盘空间大小):ulimit -a unlimited
    • 1.解引用空指针(11号信号 SIGSEGV)
      • Segmentation fault:段错误
    • 2.内存访问越界(11号信号 SIGSEGV)
      • 这种越界场景并没有让进程崩溃,原因:操作系统容忍进程访问不属于自己的内存,但是有个前提条件,越界访问的内存,没有分配给其他进程所使用。
      • 当操作系统发现进程在越界访问其他进程的内存时,操作系统就会给当前进程发送一个11号信号,让当前进程退出。
    • 3.除0(8号信号 SIGFPE)
    • 4.double free (收到6号信号)

4.信号的处理方式

  • 操作系统对信号的处理方式 (man 7 signal)
    • 默认处理方式:
      • SIG_ DFD,操作系统当中已经定义号信号的处理方式了
        • 2 号信号->终止进程
        • 11号信号 -> 终止进程,并且产生核心转储文件
    • 忽略处理方式:
      • SIG_ IGN, 该信号为忽略处理(僵尸进程),进程收到忽略处理方式的信号后,是不进行处理的。
      • SIGCHLD信号
        • 子进程先于父进程退出,子进程退出的时候会给父进程发送SIGCHLD信号,而父进程接收到这个信号之后,是忽略处理的,导致了父进程并没有回收子进程的退出状态信息,从而子进程变成了僵尸进程。
    • 自定义处理方式:
      • 程序员可以更改信号的处理方式, 定义一个函数, 当进程收到该信号的时候, 调用程序员自己写的函数。

5.信号的注册

    • 基础概念了解:
    • 一个进程收到一个信号,这个过程称之为注册,信号的注册和注销并不是一个过程,是两个独立的过程。
    • 内核中信号注册
    • 位图以及sigqueue队列的的了解
    • task_ struct结构体内部
      • struct sigpending pending;
      • sigset_ t
    • 三者之间的联系
    • 信号的注册:
    • 位图更改为1,添加sigqueue节点到sigqueue队列
      • 信号注册的时候,会将信号对应的比特位从0修改为1,表示当前进程收到了该信号。还需要在sigqueue队列当中添加一个sigqueue节点,队列在操作系统内核当中本质上就是一个双向链表(先进先出的特性)。
    • 区别:
      • 非实时信号的注册
        • 第一次注册:
          • 修改sig位图(0->1),修改sigqueue队列
        • 第二次注册相同信号值的信号:
          • 修改sig位图(1->1),并不会添加sigqueue节点,因此,对于非实时信号,相同信号的sigqueue节点在sigqueue队列当中有且只有一个
      • 实时信号的注册
        • 第一次注册:
          • 修改sig位图(0->1),修改sigqueue队列
        • 第二次注册相同信号值的信号:
          • 修改sig位图(1->1),添加sigqueue节点到sigqueue队列当中

6.信号的注销:

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

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

  • 7.1 自定义处理方式, 就是让程序员自己定义某一个信号的处理方式.
  • 7.2函数
    • sighandler_t signal(int signum, sighandler_t handler);
      • signum:信号值
      • handler:更改为哪一个函数处理,接受一个函数地址,函数指针
        • typedef void (*sighandler_t) (int) ;
        • 注意回调
        • 在调用signal函数的时候,给第二个参数传递函数地址的时候,并没有调用传递的函数。而是,等到进程收到了某个信号之后,才回调刚刚注册的函数
      • 验证:
      • 如果2号和3号信号被同时自定义了,可以用9号信号终止进程
      • 9号信号(强杀)是不能被程序员自定义处理的信号。
  • 7.4 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_falgs的值为SA_SIGINFO的时候,信号按照sa_sigaction保存的函数地址进行处理
        • (sigset_ t)  sa_mask;//当进程在处理信号的时候,如果还在收到信号,则放到该信号位图当中,后续在放到进程的信号位图当中(假设进程正在处理信号A的时候,又来了一个信号B,则信号B会被放到sa_mask这各位图当中,等到进程及那个信号A处理完成后,再把信号B放到PCB的位图中去)
        • int   sa_ flags;
        • void    (*sa restorer) (void);//保留字段
      • };
      • 测试1:自定义处理方式
      • 测试2:先以自定义方式处理,再以默认方式处理
  • 7.5内核理解自定义原理

8.信号的捕捉流程

  • 8.1信号的处理时机:当从内核态切换会用户态的时候,会调用do signal函数处理信号(判断进程是否收到信号)
    • 有,就处理信号(信号的处理方式(默认, 忽略, 自定义) )
    • 没有,就直接返回会用户态
  • 8.2处理信号的时候, 不同的处理方式,
    • 默认,忽略:直接在内核就处理结束。
    • 自定义处理:调用程序员自己定义的处理函数进行处理
      • 执行用户自定义的处理函数(用户空间)
      • 调用sigreturn()再次回到操作系统内核(内核空间)
      • 再次调用会调用do_ signal 函数处理信号
      • 调用sys_ sigreturn函数回到用户空间,继续执行代码
  • 8.4常见的进入到内核的方式:
    • 一调用系统调用函数,
    • 内存访问越界,访问空指针
    • 调用库函数

9.信号的阻塞.

  • 9.1要理解的点:. 信号的注册是信号注册,信号阻塞是信号阻塞。信号的阻害并不会干扰信号的注册,而是说进程收到这个信号之后,由于阻寒, 暂时不处理该信号。
    • 阻塞并不是说不处理该信号,而是等该信号不阻塞了之后,在进 行处理
  • 9.2从内核理解:
  • 9.3加上信号阻塞之后, 理解信号的处理;
    • 进入内核, 返回之前, 会调用do signal函数处理信号,有信号要处理, 则先判断该信号是否阻塞, 如果没阻塞, 再处理信号。如果阻塞, 则不处理。
  • 9.4接口:
    • int sigprocmask(int how, const sigset_t *set,sigset_ t *oldset);
      • how:想让sigprocmask做在么事情
        • SIG_BLOCK: 设置某个信号为阻塞状态
        • SIG_UNBLOCK :设置某个信号为非阻塞状态
        • SIG_ SETMASK :用第二个参数“set”,替换原来的阻塞位图。
      • set:新设置的阻塞位图
      • oldset :原来老的阻塞位图
        • set:传递进去的是一个位图类型的变量,根据传递进函数的变量,计算新的阻塞位图
        • 可能出现的结果:
        • 1.阻塞单个信号/阻塞多个信号
        • 2.解除阻塞单个信号/解除阻塞多个信号
    • 原理解析:
      • 当how为SIG_BLOCK时,函数会根据set,计算新的阻塞位图,方式为:block(new) = block(old) | set;
        • 验证:
        • 结论:9号信号和19号信号是不能被阻塞的
      • 当how为为SIG_UNBLOCK时,函数会根据set,计算新的阻塞位图,方式为:block(new) = block(old) & (~ set) ;
        • 验证:
        • 结论:相同非实时信号的注册和注销只处理一次
      • 当how为为SIG_SETMASK时,函数会根据set,计算新的阻塞位图, 方 式为:block (new) = set;

10.扩展

  • 1.父子进程+进程等得+自定义信号处理方式
  • 2.volatile关键字
  • 作用:保证内存可见性
    • 每次CPU要计算的数据都是从内存当中获取,拒绝编译时优化的方案(从寄存器当中获取)
    • gcc/g++的编译选项“-O0,-O1, -O2, -O3年 ,优化级别时越来越高。( 理解优化级别越高,程序 可能执行的越快)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值