操作系统 Chap.4 中断 / 异常 / 信号量
1. 引子: 模式转换(Mode Switch)
所谓模式转换, 意味着程序在运行过程中在不同的CPU模式之间切换.
先前提到过的CPU模式: (详见Chap.2中1.3的内容)
User Mode: 用户态(目态)
Kernel Mode: 内核态(管态)
所以, 到底什么时候会出现模式切换?
- 系统调用(System Calls)
- 中断(Interrupts)
- 异常(Exceptions)
我们在此前的文章中已经提及了系统调用的内容(详见Chap.2中2.3), 本文我们将对中断与异常进行梳理总结.
2. 异常(Exceptions)
2.1 异常的分类
通常我们能遇到的异常分两类:
- 程序异常(Programmed exceptions / Traps )
- int 0x80; 这是发出系统调用的旧方法
- 异常运行(Anomalous executions / Faults )
- $ a/0 $ ; 除0
- p = NULL; a = *p; 引用空指针
2.2 异常的处理过程
说到这了, 异常到底是怎么被捕获 / 处理的呢?
我们通过 除0 这一典型问题来说明这一过程:
- CPU在运行除法操作时发现了一个异常(即除0操作);
- CPU运行模式切换为内核态(Kernel Mode), 随后对应异常的处理程序(此处即除0异常处理程序)被自动调用.
- 该处理程序会自动发送 SIGFPE(Float Point Exception) 信号给产生该异常的进程, 随后CPU模式返回用户态
- 进行判断:
- 如果该进程定义了浮点异常信号的处理程序, 则执行该程序;
- 若否, 则直接杀死该进程
总结一下:
异常在出现的一瞬间会被处理器捕获, 并转向内核态唤醒异常处理程序(Exception Handlers). 随后异常处理程序通常会将该异常转化为一个在用户态能够表示的信号量(SIGNAL), 并退出内核态.
2.3 终止进程
通常的终止方式有以下几种:
- kill -signame pid / kill(pid, sig)
- pid的含义并不很简单:
- pid > 0: 意味着指向某个真实的进程
- pid == 0: 意味着 发送异常的进程的同组全部进程
- pid == -1: ;意味着 发送异常的进程有权发送信号的全部进程
- pid的含义并不很简单:
- pthread_kill()
- 用于杀死某个发送进程的指定线程
- tgkill(pid, tid, sig)
- sigqueue(pid, sig, value)
需要明确, 当一个进程被终止时, 其父进程会收到 SIGCHILD 信号
2.4 信号处理程序
我们在2.2异常处理的最后一步中涉及到了一个判断.
如果该进程定义了浮点异常信号的处理程序, 则执行该程序;
此即 信号处理程序(Signal handler) 的意义所在.
正常情况下, 系统针对不同的信号有自己的一套处理机制, 比如上文中遇到SIGFPE信号时, 系统默认的反应是直接杀死该进程.
但当用户在自己的进程中定义了针对该信号的处理机制时, 系统会优先遵循用户的定义.
系统提供了两种函数来进行默认处理机制的更改:
- signal(): 通常不推荐使用该函数. 当你定义的处理程序被调用一次后, 它就会失效, 后续的处理继续遵循系统默认的设置.
- sigaction(): 相比前者功能更全面, 如:
- 当定义的处理程序被调用时, 会自动阻塞其它的信号
- 如果阻塞中的系统调用(比如read / write)被信号处理程序打断, 在信号处理程序执行完毕后, 系统调用会自动重启.
事实上, Linux最新版本的signal()已经进行了重新设计, 但本质上它也是通过调用sigaction()来实现的. 并且我们不能确定其他的Unix系统是否如此设计, 因此仍然不建议使用signal()函数.
3. 中断 vs 异常?(Interrupt vs Exception)
在Chap.1的2.3节中, 我们曾经提过 中断(Interrupt) 这个事.
这里我们还得明确一下中断跟异常的关系与区别.
A trap or exception is a software-generated interrupt caused either by an error or a user request.
陷阱 / 异常是系统自动产生的中断, 它由错误或用户请求引起.
所以实际上, 中断 / 异常出现时, 系统要干的活是差不多的:
- 切到内核模式
- 保存当前上下文
- 通过IDT(Interrupt Descriptor Table)调用对应的处理程序
- 恢复现场
- 切回用户模式
关于IDT, 这玩意有点类似于我们在Chap.1中2.3节提到过的 Interrupt Vector Table 的升级版.
在IVT中, 这个表只会存储每个中断服务例程的具体地址. 而在IDT中, 还会存储对应的类型信息 / 权限级别等信息.
本篇内容较少, 这是因为其实这里的有些内容在前面已经有所涉及了, 本篇权当对前面的内容的进一步引申与阐述.
本篇博文就到这里~