这三个概念和C语言有什么关系呢? 中断这个词恐怕人民群众都不陌生。很多人把中断分为两种:硬件中断和软件中断。其实怎么叫关系都不大,关键是我们要明白他们之间的异同点。 我比较喜欢把 “中断”,分为三种即陷阱,中断和异常,似乎记得Intel是这么划分的(这句话我不保证正确,有兴趣的人自己看一下 Intel 的手册)。它们的英文分别是 trap,interrupt 和 exception。 陷阱 (trap): 大家都知道,现代的CPU都是有优先级概念的,用户程序运行在低优先级,操作系统运行在高优先级。高优先级的一些指令低优先级无法执行。有一些操作只能由 操作系统来执行,用户想要执行这些操作的时候就要通知操作系统,让操作系统来执行。用户态的程序就是用这种方法来通知操作系统的。 具体怎样做的呢?操作系统会把这些功能编号,比如向一个端口写一个字符的功能调用编号 12,有两个参数,端口号 port 和写入的字符 bytevalue。我们可以如下实现:(这个例子无法编译,但是这种汇编和 C 混合编程的风格微软的编译器支持,十分好用,顺便夸一句微软,他们的编译器是我用过得最优秀的商业编译器) int outb(int port, int bytevalue) { __asm mov r0, 12; /* 功能号 */ __asm mov r1, port; /* 参数 port */ __asm mov r2, bytevalue; /* 参数 bytevalue */ __asm trap /* 陷入内核 */ return r0; /* 返回值 */ } 在操作系统的 trap 处理的 handler 里面,相信大家已经知道怎么办了。一部分C的库函数是用这种方法实现的。 中断: 中断我们这里专指来自于硬件的中断,通常分为电平触发和边沿触发(请参考数字电路)。简单的说就是CPU每执行完一条都去检测一条管腿的电平是否变化。如 果满足条件,CPU转向事先注册好的函数。系统中最重要的一个中断就是我们经常说的时钟中断。为什么要说这个呢?这和C程序有什么关系呢?书上说了中断是 由操作系统处理的,操作系统会保存程序的现场啊,用户程序根本感觉不到中断的存在啊。书上说得没错,但是它有两件事情没有告诉你: 1. 线程调度策略。 2. 程序的现场不包括什么? 这里插一句话表达对国内操作系统教材作者的敬仰,他们是怎么把操作系统拆成一块一块儿的呢?进程管理,线程调度,内存管理,中断管理,IPC,都是互相关联的。笔者十分怀疑分块讨论的意义到底有多大。 先回答第一个问题,线程调度时机。在哪些情况下操作系统会运行scheduler 呢?现代操作系统调度的基本单位都是线程,所以我们不讨论进程的概念。 1. 一些系统调用 2. I/O 操作 3. 一个线程创建 4. 一个线程结束 5. mutex lock 6. P semaphore 7. 硬件中断 / 时钟中断 8. 主动放弃 CPU,比如 sleep(), yield() 9. 给另外一个线程发消息,信号 10. 主动唤醒另外一个线程 11. 进程结束 : : 我记不住那么多了,应该还有。 第二个问题,现场不包括什么。至少不包括全局变量。 于是就有了一个经典的面试题: int a; void thread_1() { for (;;) { do something; a++; } } void thread_2() { for (;;) { do something; a--; } } main() { create_thread(thread_1); create_thread(thread_2); } 因为 a++,a--,可能并不是一条汇编语言(RISC CPU),它会被中断打断,而中断又会引起线程调度。有可能将另外一个线程投入运行。所以结果是无法预测的。讨论这个问题的文章很多,就不多费口舌了。 提个思考题,操作系统内部,中断和中断之间,中断和线程之间,怎么保护临界资源的呢?多个 CPU 之间呢? 异常:exception 异常是指一条指令会引起 CPU 的不快,比如除零。有群众说了,如果我除零错了,操作系统把我终止了不就完了,我回去改程序,改对了重新运行不就行了么。 但是有时候CPU希望操作系统能够排除这个异常,然后CPU重新尝试去执行这条引起异常的指令。这有什么用呢?
一个十分重要的异常,缺页异常。 现代的CPU都支持虚拟内存管理,我们还是在虚拟CPU上讨论这个问题,上面说过,CPU使用2级页表映射,页面大小4K。实在懒得写如何映射了,请大家参考 Intel MMU 手册。因为重点不在这里。看下面的语句: char *p = (char *)malloc(100 * 1024 * 1024); 有人说,没什么不同啊,只不过申请的内存稍微有点儿多啊。但操作系统真地给你那么多内存了么?如果这样的程序来上几个,系统内存岂不是早被耗光?
实际上操作系统并没有真正分配物理页面而是采用了在我国盛行的一种机制:打白条!申请内存的时候操作系统仅仅在虚空间中分配了内存,也就是说仅仅是标记 着,这100M的内存归你用,但是我先不给你,当你真的用的时候我再给你分配,这个分配指的就是实实在在的物理页面了。具体怎么实现的呢?看下面的语句发 生了什么? p[0x4538] = 'A'; 有人疑问了,普通的赋值语句啊。没错,但是这条赋值语句执行了两次(这可不一定啊,我没说绝对,只是在介绍一种机制),第一次没成功,因为发生了缺页异 常,刚才说了操作系统仅仅是把这 100M 内存分配给用户了,但是没有对应真正的物理页面。操作系统并没有为 p+0x4538 所在的页面建立页表映射。所以缺页异常发生了。然后操作系统一看这个地址是已经分配给你了,我给你找个物理页面,给你建立好映射,你再执行一次试试。就这 一点来说,操作系统比我们的某些官老爷信誉要良好的多,白条兑现了。 于是第二次执行成功了。有人看到这里已经满头雾水了,这个老家伙到底想说什么? 注意到了么,操作系统要分配一个空闲页面,找不到怎么办?页面交换,找个倒霉蛋,把它的一部分页面写到硬盘上,实际上操作系统只要空闲物理页面少于一定的 程度就会做 swap。那么,如果你有个程序需要较高的效率,较好的反应速度,算法写得再好也没用,一个页面被交换出去全完。 所以,优化程序,了解操作系统的运行机制是必要的,当然优化程序绝不仅仅是这些。所以一个优秀的程序员十分有必要知道,你的程序到底运行在“什么”上面。 稍微总结一下: 陷阱:由 trap 指令引起,恢复后 CPU 执行下一条指令 中断:由硬件电平引起,恢复后 CPU 执行下一条指令 异常:由软件指令引起,恢复后 CPU 重新执行该条指令 有个牛人说过,Oracle 的数据库为什么总比别人的快一点点呢?因为那批人是写操作系统的。
感谢那位牛人,是他的指导让我进入了操作系统内核世界,虽然我不久便出来了。请您原谅我的不能追随。真心祝愿您的梦想成真