第八章 异常控制流 第五节 信号

1、信号简介

一个信号就是一条小消息, 它通知进程系统中发生了一个某种类型的事件。
低层的硬件异常是由内核异常处理程序处理的,正常情况下, 对用户进程而言是不可见的。信号提供了一种机制, 通知用户进程发生了这些异常。

1. 常见信号

  • SIGFPE
    一个进程试图除以0
  • SIGILL
    一个进程执行一条非法指令
  • SIGSEGV
    进程进行非法内存引用
  • SIGINT
    如果当进程在前台运行时,那么内核就会发送一个SIGINT信号给这个前台进程组中的每个进程
  • SIGKILL
    一个进程可以通过向另一个进程发送一个SIGKILL信号强制终止它
  • SIGCHLD
    当一个子进程终止或者停止时, 内核会发送一个SIGCHLD信号给父进程

2、信号术语

传送一个信号到目的进程是由两个不同步骤组成的

1. 发送信号

1.发送信号的原因

  1. 内核检测到了一个系统事件
  2. 一个进程调用了kill函数

2. 说明

  1. 内核通过更新目的进程上下文中的某个状态发送信号
  2. 一个进程可以发送信号给自己

2. 接受信号

当目的进程被内核强迫以某种方式对信号的发送做出反应时, 它就接收了
信号。

1. 进程的操作

  • 忽略这个信号
  • 终止
  • 执行一个称为信号处理程序(signalhandler)的用户层函数捕获这个信号
    在这里插入图片描述

2. 待处理信号

一个发出而没有被接收的信号叫做待处理信号(pending signal) 。

1. 在任何时刻,一种类型至多只会有一个待处理信号

其含义是

  • 如果进程有一个类型为k的待处理信号, 那么任何接下来发送到这个进程的类型为k的信号都不会排队等待,它们被简单地丢弃。
  • 如果进程有选择性地阻塞接收某种信号时,它仍可以被发送, 但是产生的待处理信号不会被接收, 直到进程取消对这种信号的阻塞。
2. 一个待处理信号最多只能被接收一次

内核为每个进程在pending 位向量中维护着待处理信号的集合, 而在blocked 位向量(也称为信号掩码(signal mask)) 。中维护着被阻塞的信号集合。只要传送了一个类型为K 的信号, 内核就会设pending 中的第k 位, 而只要接收了一个类型为K 的信号, 内核就会清除pending 中的第k 位。

3、发送信号

1.进程组

  1. 进程组用一个正整数表示
  2. 每个进程只能属于一个进程组
  3. 父进程和子进程默认属于一个进程组

2. 使用/bin/kill程序发送信号

linux> /bin/kill -9 12123
linux> /bin/kill -9 -12123
  1. 正的pid代表发送信号9向进程号为12123的进程
  2. 负的pid代表发送信号9向进程组号为12123的进程组的所有进程

3. 从键盘发送信号

比如我们使用shell的时候按下Ctrl + c就会发送一个信号到前台进程组的每个进程中。

Unix shell的 作业
Unix shell使用作业(job)这个抽象概念来表示为对一条命令行求值而创建的进程
在任何时刻,至多只有一个前台作业和0个或多个后台作业。
比如,键入
linux> ls I sort
会创建一个由两个进程组成的前台作业,这两个进程是通过Unix管道连接起来的: 一个
进程运行ls程序,另一个运行sort程序。
shell 为每个作业创建一个独立的进程组。进程组ID 通常取自作业中父进程中的一个。
在这里插入图片描述

4. 用kill函数发送信号

可以使用该函数将信号发送给其他进程,包括他自己。
在这里插入图片描述
pid大于0,则是发送信号到进程pid。等于0发送到调用该函数的进程所在进程组的所有进程。小于0则是给|pid|进程组的所有进程。

5.使用alarm函数发送信号

在这里插入图片描述
alarm函数安排内核在secs秒后发送一个SIGALRM信号给调用进程。

4、接收信号

当内核把进程p从内核模式切换到用户模式时。它会检查进程p 的未被阻塞的待处理信号的集合

  • 如果这个集合为空(通常情况下), 那么内核将控制传递到p的逻辑控制流中的下一条指令
  • 如果集合是非空的, 那么内核选择集合中的某个信号k(通常是最小的k )并且强制p接收信号k

1. 信号的默认行为

每个信号都有一个默认的行为
在这里插入图片描述
举例
SIGKILL 默认类型就是终止接收进程
SIGCHLD 默认就是忽略这个信号
信号可以通过signal函数修改默认行为,但是SIGSTOPSIGKILL的默认行为不可以修改。

信号处理程序可以被其他信号处理程序中断
在这里插入图片描述

5、阻塞和解除阻塞信号

1. 隐式阻塞机制

默认阻塞任何当前处理程序正在处理信号类型的待处理的信号

2. 显式阻塞机制

应用程序可以使用sigprocmask函数和它的辅助函数, 明确地阻塞
和解除阻塞选定的信号

相关函数如下
在这里插入图片描述

1. 原理

sigprocmask函数改变当前阻塞的信号集合,也就是blocked位向量,来显示的阻塞信号。
具体的行为依赖how的值,有以下三种情况
在这里插入图片描述

6、编写信号处理程序

1. 安全的信号处理

因为信号处理程序要跟主程序以及其他信号处理程序并发,所以我们需要进行安全的信号处理。书中给出了一些原则:

  1. 处理程序要尽量简单
  2. 处理程序中只调用异步信号安全的函数
    要么它是可重入的(例如只访问局部变量),要么它不能被信号处理程序中断
  3. 阻塞所有的信号,保护对共享全局数据结构的访问。
    如果处理程序和主程序或其 他处理程序共享 个全局数据结构,那么在访问(读或者写)该数据结构时,你的处理程序和主程序应该暂时阻塞所有的信号
  4. 用volatile 声明全局变量
    考虑一个处理程序和一个main 函数,它们共享一个全局变量g。处理程序更新g, main 周期性地读g。对于一个优化编译器而言,main 中g的值看上去从来没有变化过, 因此使用缓存在寄存器中g的副本来满足对g的每次引用是很安全的。如果这样, main 函数可能永远都无法看到处理程序更新过的值。可以用volatile类型限定符来定义一个变量,告诉编译器不要缓存这个变量。例如:
    volatile int g;
    volatile 限定符强迫编译器每次在代码中引用g 时, 都要从内存中读取g的值。一般来说, 和其他所有共享数据结构一样,应该暂时阻塞信号,保护每次对全局变量的访问。
  5. 用sig_atomic_t 声明标志。
    在常见的处理程序设计中, 处理程序会写全局标志来记录收到了信号。主程序周期性地读这个标志, 响应信号,再清除该标志。对通过这种方式来共享的标志, C 提供一种整型数据类型sig_atomic_t, 对它的读和写保证会是原子的(不可中断的),因为可以用一条指令来实现它们:volatile sig_atomic_t flag;
    因为它们是不可中断的, 所以可以安全地读和写该变量, 而不需要暂时阻塞信号。注意, 这里对原子性的保证只适用于单个的读和写, 不适用于像flag++或flag=flag+10 这样的更新, 它们可能需要多条指令。

2. 正确的信号处理

信号是不排队的,当pending位向量中某个类型的信号已经在处理中,后面到的信号就会被简单丢弃。所以如果出现了一个信号的时候,就证明至少有一个信号到达了,而不是只有一个信号到达了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值