1. 进程信号基本理解
- 信号是一种事件的异步通知机制
- 信号的产生,相对于进程的运行是异步的
- 信号是要发送给进程的
- 在信号产生之前,信号的处理方法已经准备好了
- 信号产生 -》 信号保存 -》信号处理
1.1 查看信号
linux
$ kill -l

1.2 查看信号的作用
linux
$ man 7 signal
- Term(终止)
- Core(终止)
- Ign(忽略)
- Stop(暂停)
- Cont(继续)

1.3 signal 改变信号执行动作
cpp
signal(SIGINT/*2*/, handler); //让2号信号的处理动作变成执行handler函数
- SIG_IGN:忽略信号
- SIG_DFL:信号的默认处理动作
- handler:自定义的处理函数
cpp
typedef void (*__sighandler_t) (int); //handler要遵守这个格式
2. 产生信号
2.1 通过终端按键产生信号
- 给前台进程发信号
- ctrl + c(SIGINT):发送终止信号
- ctrl + \(SIGQUIT):发送终止信号并生成core dump文件,用于事后调试
- ctrl+ z(SIGTSTP):发送停止信号,将前台进程挂起到后台
- jobs:查看后台进程
- bg 任务号:让进程恢复执行
- fg 任务号:把进程提到前台
2.2 调用系统命令向进程发信号
- 例如
linux
$ kill -9 进程号 # 关闭进程
2.3 使用函数产生信号
2.3.1 kill
cpp
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
- 参数列表
- pid:要对哪个进程发送信号
- sig:要发送什么信号
- 返回值:
- 成功,返回 0
- 失败,返回 -1
2.3.2 raise
- raise 函数可给当前进程发送指定的信号(自己给自己)
cpp
#include <signal.h>
int raise(int sig)
2.3.3 abort
- abort 函数是当前进程接收到信号而异常终止(6号信号)
cpp
#include <stdlib.h>
void abort(void);
2.4 由软件条件产生信号
cpp
#include <unistd.h>
unsigned int alarm(unisgned int seconds);
- 功能:告诉内核在 seconds 秒之后给当前进程发送 14)SIGALRM 信号,该信号的默认处理动作是终止当前进程
- 闹钟设置一次起效一次
2.5 硬件异常产生信号
- 除0:8号信号
- 内存越界:11号信号
3. 保存信号
3.1 信号其他相关常见概念
- 实际信号的处理动作称为信号递达
- 信号从产生到递达之间的状态,称为信号未决
- 进程可以选择阻塞某个信号
- 被阻塞的信号产生时将保持在未决状态,知道进程解除对此信号的阻塞,才执行递达的动作
- 阻塞和忽略
- 阻塞:只要信号被阻塞就不会递达
- 忽略:在递达之后可选的一种处理动作
- 9号信号不可被捕捉,不可被阻塞
3.2 在内核中的表示

- block 是阻塞表,1表示第几号信号阻塞
- 默认情况下,进程不对任何信号进行屏蔽
- 置1的信号不会被进程处理(挂起)
- pending 是未决表,1 表示收到了第几号信号
- 常规信号在递达之前产生多次只记一次
- handler:函数指针数组,表示如何处理
- SIG_DFL:默认
- SIG_IGN:忽略
- 自定义处理动作
3.3 sigset_t
- sigset_t 称为信号集
- 阻塞信号即也叫做当前进程的信号屏蔽字(Signal Mask)
- 每个信号只有一个 bit 的未决标志,非 0 即 1
3.4 信号集操作函数
cpp
#include <signal.h>
int sigemptyset(sigset_t *set); // 屏蔽字置空
int sigfillset(sigset_t *set); // 初始化信号集
int sigaddset(sigset_t *set,int signo); // 添加一个信号
int sigdelset(sigset_t *set,int signo); // 删除一个信号
int sigismember(const sigset_t *set, int signo); // 判断一个信号集的有效信号中是否包含某种信号
3.4.1 sigprocmask
- sigprocmask 可以读取或更改进程的信号屏蔽字(阻塞信号集)
cpp
#include <signal.h>
int sigprocmask(int how,const sigset_t *set,sigset_t *oset);
- 返回值:
- 成功为0
- 失败为-1
- 参数列表
- how:如何更改
- SIG_BLOCK:set 为添加信号屏蔽字的信号,设置1
- SIG_UNBLOCK:set 为要解除阻塞的信号,设置0
- SIG_SETMASK:将 set 指向信号屏蔽字设置为对应的值(比较方便,之间设置1,0)
- set:对哪个信号集进行操作
- oset:输出型参数
- how:如何更改
3.4.2 sigpending
- 准备递达之前,要先情况 pending 信号集即中对应的信号位图 1->0
- 如果每个信号产生多次,只能记录一次
cpp
#include <signal.h
int sigpending(sigset_t *set);
- 功能:读取当前进程的未决信号集,通过 set 参数传出
4. 捕捉信号
4.1 捕捉信号的流程


- 合适的时候处理信号
- 进程从内核态,返回到用户态的时候进行信号检测
- 如果信号是默认的呢?忽略呢?
- 忽略的话把pending的对应位置置0就行,默认就去执行默认动作
4.2 sigaction
cpp
#include <signal.h>
int sigaction((int signo, const struct sigaction *act, struct sigaction *oact);
- 功能:读取和修改与指定信号相关联的处理动作
- 参数列表:
- signo:信号编号
- act:如何处理信号(自定义捕捉方法)
- oact:输出型参数,历史动作
- 返回值:
- 成功:0
- 失败:-1
- 当某个信号的处理函数被调用的时候,内核自动将当前信号加入进程的信号屏蔽字,当信号处理处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号的时候,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止
4.3 操作系统是如何运行的
4.3.1 硬件中断
- OS 不需要关注外部设备是否准备好了,而是外部设备准备好了会叫操作系统
- 当没有中断时OS是暂停的
- 信号是纯软件,本质是用软件来模拟硬件中断
4.3.2 时钟中断
- 以固定的、特定的频率向CPU发送特定的中断
- 操作系统就是在硬件时钟中断的驱动下进行调度
- 操作系统是基于中断进行工作的软件
4.3.3 死循环
- 操作系统的本质就是一个死循环!
4.3.4 软中断
- 让CPU内部触发中断逻辑
- 系统调用过程中,其实就是先让 int 0x80、syscall 陷入内核,本质就是触发软中断
- OS 不提供任何系统调用接口!OS 只提供系统调用号
- 缺页中断、内存碎片处理、除零、野指针等等
- 全部都会转换成为CPU内部的软中断
- CPU内部的软中断,比如 int 0x80、syscall 叫做陷阱(主动发起,以及发起的目的合法)
- CPU内部的软中断:比如除零/野指针等,叫做异常(不合法)
4.4 如何理解用户态和内核态
- 操作系统无论如何切换进程,都能找到同一个操作系统
- 用户态就是执行用户 [0,3]GB 所处的状态
- 内核态就是执行内核 [3,4]GB 所处的状态(所有进程共享一份!)
- 所有进程执行的内核态的位置相同
- OS 为了保护自己,不相信任何人!必须以系统调用的方式访问 [3,4]GB
- 用户态:以用户身份,只能访问自己的 [0,3GB]
- 内核态:以内核身份,运行通过系统调用的方式,访问OS [3,4GB]
- 系统中,用户或者OS自己,怎么直到当前处于内核态,还是用户态?
- 先不考虑,很复杂。简单来说就是,由硬件决定,
- 有个cs寄存器(其中的CPL字段)0:表示内核态;3表示用户态
5. 可重入函数
- 如果⼀个函数只访问自己的局部变量或参数,则称为可重入函数
- 如果一个函数符合下列条件之一则是不可重入的
- 调用了 malloc 或 free ,因为 malloc 也是用全局链表来管理堆的
- 调用了标准 I/O 库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构
6. volatile
- 极端情况下,例如编译器的优化级别比较高,将 main 函数中未被修改的变量优化到了寄存器(读取比较快)中,会覆盖进程看到的变量的真实情况
- 每次都从内存中找数据,保证内存空间的可见性
- 作用:保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进行操作
cpp
volatile int flag = 0;
7. SIGCHLD
- 子进程在终止时会给父进程发 SIGCHLD 信号,默认处理动作是忽略
- 父进程可以自定义 SIGCHLD 处理函数
- 系统默认行为:对于 SIGCHLD 信号,系统的默认动作(SIG_DFL)是忽略,但这种 "忽略" 是特殊的:
- 子进程终止后,内核会保留其退出状态,等待父进程调用 wait() 或 waitpid() 来 "收尸"。
- 如果父进程不处理,子进程就会变成僵尸进程(Zombie),其进程描述符会一直占用系统资源。
- 显式设置 SIG_IGN:当你通过 sigaction() 或 signal() 将 SIGCHLD 的处理动作显式设置为 SIG_IGN 时,这是一种完全不同的行为:
- 子进程终止时,内核会直接丢弃其退出状态,并自动回收所有资源,不会产生僵尸进程。
- 父进程也不会收到任何通知,调用 wait() 会立即失败并返回 -1。
cpp
signal(SIGCHLD, SIG_IGN); // 忽略子进程发送的SIGCHLD信号(丢弃退出状态,自动回收资源,不会产生)