进程上下文和中断上下文、原子上下文的区别

  内核空间和用户空间是现代操做系统的两种工做模式,内核模块运行在内核空间,而 用户态应用程序运行在用户空间。它们表明不一样的级别,而对系统资源具备不一样的访问权限。内核模块运行在最高级别(内核态),这个级下全部的操做都受系统信 任,而应用程序运行在较低级别(用户态)。在这个级别,处理器控制着对硬件的直接访问以及对内存的非受权访问。内核态和用户态有本身的内存映射,即本身的 地址空间。程序员

  系统的两种不一样于行状态,才有了上下文的概念。用户空间的应用程序,若是想请求系统服务,好比操做某个物理设备,映射设备的地址到用户空间,必须经过系统调用来实现。(系统调用是操做系统提供给用户空间的接口函数)。以下图所示:并发

 

  经过系统调用,用户空间的应用程序就会进入内核空间,由内核表明该进程运行于内核空间,这就涉及到上下文的切换,用户空间和内核空间具备不一样的 地址映射,通用或专用的寄存器组,而用户空间的进程要传递不少变量、参数给内核,内核也要保存用户进程的一些寄存器、变量等,以便系统调用结束后回到用户 空间继续执行,所谓的进程上下文,就是一个进程在执行的时候,CPU的全部寄存器中的值、进程的状态以及堆栈上的内容,当内核须要切换到另外一个进程时,它 须要保存当前进程的全部状态,即保存当前进程的进程上下文,以便再次执行该进程时,可以恢复切换时的状态,继续执行。异步

  同理,硬件经过触发信号,向CPU发送中断信号,致使内核调用中断处理程序,进入内核空间。这个过程当中,硬件的一些变量和参数也要传递给内核, 内核经过这些参数进行中断处理,中断上下文就能够理解为硬件传递过来的这些参数和内核须要保存的一些环境,主要是被中断的进程的环境。函数

  Linux内核工做在进程上下文或者中断上下文。提供系统调用服务的内核代码表明发起系统调用的应用程序运行在进程上下文;另外一方面,中断处理程序,异步运行在中断上下文。中断上下文和特定进程无关。性能

  运行在进程上下文的内核代码是能够被抢占的(Linux2.6支持抢占)。可是一个中断上下文,一般都会始终占有CPU(固然中断能够嵌套,但咱们通常不这样作),不能够被打断。正由于如此,运行在中断上下文的代码就要受一些限制,不能作下面的事情:atom

1. 睡眠或者放弃CPU。操作系统

  因为中断上下文不属于任何进程,它与current没有任何关系(尽管此时current指向被中断的进程),因此中断上下文一旦睡眠或者放弃CPU,将没法被唤醒。因此也叫原子上下文(atomic context)。递归

2. 尝试得到信号量接口

  为了保护中断句柄临界区资源,不能使用mutexes。若是得到不到信号量,代码就会睡眠,会产生和上面相同的状况,若是必须使用锁,则使用spinlock。进程

3. 执行耗时的任务

  中断处理应该尽量快,由于内核要响应大量服务和请求,中断上下文占用CPU时间太长会严重影响系统功能。在中断处理例程中执行耗时任务时,应该交由中断处理例程底半部来处理。

4. 访问用户空间的虚拟地址

  由于中断上下文是和特定进程无关的,它是内核表明硬件运行在内核空间,因此在终端上下文没法访问用户空间的虚拟地址

5. 中断处理例程不该该设置成reentrant(可被并行或递归调用的例程)。由于中断发生时,preempt和irq都被disable,直到中断返回。因此中断上下文和进程上下文不同,中断处理例程的不一样实例,是不容许在SMP上并发运行的。

6. 中断处理例程能够被更高级别的IRQ中断。若是想禁止这种中断,能够将中断处理例程定义成快速处理例程,至关于告诉CPU,该例程运行时,禁止本地CPU上全部中断请求。这直接致使的结果是,因为其余中断被延迟响应,系统性能降低。

内核的一个基本原则就是:在中断或者说原子上下文中,内核不能访问用户空间,并且内核是不能 睡眠的。也就是说在这种状况下,内核是不能调用有可能引发睡眠的任何函数。通常来说原子上下文指的是在中断或软中断中,以及在持有自旋锁的时候。内核提供 了四个宏来判断是否处于这几种状况里:

#define in_irq()     (hardirq_count()) //在处理硬中断中
#define in_softirq()     (softirq_count()) //在处理软中断中
#define in_interrupt()   (irq_count()) //在处理硬中断或软中断中
#define in_atomic()     ((preempt_count() & ~PREEMPT_ACTIVE) != 0) //包含以上全部状况

这四个宏所访问的count都是thread_info->preempt_count。这个变量实际上是一个位掩码。最低8位表示抢占计数,一般由spin_lock/spin_unlock修改,或程序员强制修改,同时代表内核允许的最大抢占深度是256。8-15位表示软中断计数,一般由local_bh_disable/local_bh_enable修改,同时代表内核允许的最大软中断深度是256。位16-27是硬中断计数,一般由enter_irq/exit_irq修改,同时代表内核允许的最大硬中断深度是4096。第28位是PREEMPT_ACTIVE标志。用代码表示就是:PREEMPT_MASK: 0x000000ffSOFTIRQ_MASK: 0x0000ff00HARDIRQ_MASK: 0x0fff0000凡是上面4个宏返回1获得地方都是原子上下文,是不允许内核访问用户空间,不允许内核睡眠的,不允许调用任何可能引发睡眠的函数。并且表明thread_info->preempt_count不是0,这就告诉内核,在这里面抢占被禁用。但 是,对于in_atomic()来讲,在启用抢占的状况下,它工做的很好,能够告诉内核目前是否持有自旋锁,是否禁用抢占等。可是,在没有启用抢占的状况 下,spin_lock根本不修改preempt_count,因此即便内核调用了spin_lock,持有了自旋锁,in_atomic()仍然会返回 0,错误的告诉内核目前在非原子上下文中。因此凡是依赖in_atomic()来判断是否在原子上下文的代码,在禁抢占的状况下都是有问题的。

相关文章
相关标签/搜索