讲解这部分之前,我们先阐述一个概念-内核控制路径:就是一段在内核态执行的代码,比如说,异常处理程序,中断处理程序,系统调用处理,内核线程等等在内核态执行的代码。所以,内核态程序被激活的方式有:
- 系统调用(异常的一种)
- 异常
- 中断
- 内核线程
上面的任意一种方式,都可以让CPU执行内核态的代码。比如,I/O设备引发一个中断,相应的内核态程序,首先,应该是保存内核态堆栈中的CPU寄存器的内容;然后,执行中断处理程序;最后,再恢复这些寄存器的内容。所以,在后面的描述中,我们使用内核控制路径
这个术语代替一段可执行的内核态代码这种表述。使用内核控制路径
的好处就是,它是从英语直译过来的,可能会更好地表达程序代码执行的顺序性,是一个过程;这样在描述中断嵌套时更有意义。
内核控制路径可以任意嵌套;如下图所示,用户态的程序被中断打断,进入内核态响应中断;而这时候又来了其它中断,就会响应最新的中断,以此类推;但是,执行完一个中断处理程序之后,会回到之前的状态执行。这样,最后又回到了用户态。
图4-3 内核控制路径的一个嵌套异常的示例
允许内核控制路径嵌套的代价就是中断处理程序不能阻塞,也就是说,中断处理程序运行时不能发生进程切换。恢复执行嵌套内核控制路径的所有数据都存储在内核态堆栈中,而该堆栈又和当前进程紧紧绑定在一起。通俗的说,中断处理程序相当于当前进程的资源,切换进程之前该中断资源必须释放掉。
假设内核没有bug,那么大部分的异常发生在用户态。实际上,要么是编程错误,要么是调试器故意触发的。而页错误
异常发生在内核态,它是内核在访问物理地址时不存在引发的异常。处理这样的异常,内核挂起当前进程,切换到新进程,直到该请求页可用。因为页错误
异常绝不会引发进一步的异常,所以,有关联的内核控制路径最多是2个(第一个是系统调用造成的,第二个是页错误造成的)。也就是说,页错误的异常最多嵌套2层。
和异常相反,尽管内核代表当前进程处理这些中断,但是,I/O设备引发的中断和当前进程没有直接数据引用的关系。事实上,给定一个中断,无法推断出是哪个进程在运行。所以,中断的执行不会引起进程的切换,也就可以无限嵌套处理。
中断处理程序可以打断中断或异常处理程序执行,但是反过来,异常不能打断中断处理程序。中断处理程序绝对不能包含页错误
的操作,因为这会诱发进程切换。
Linux嵌套执行中断或异常处理程序的两个主要原因是:
- 为了提高可编程中断控制器和设备控制器的吞吐量。内核正在处理一个中断的时候,能够及时响应另一个中断。
- 实现没有中断优先级的模型。这可以简化内核代码并提高可移植性。
在多核系统中,几个中断或异常处理程序可能会并发执行。更重要的是,异常处理程序可能由于进程切换,造成在一个CPU上启动,然后迁移到另一个CPU上执行。