如何在内核模式下进行上下文切换时保持原子性?

时间:2012-05-07 06:22:54

标签: linux operating-system linux-kernel x86 kernel

毫无疑问,内核模式中的上下文切换是由硬件中断或软件中断引起的。众所周知,上下文切换应保持原子性,但如何实现原子性?众所周知,中断门禁用所有中断(我不知道是否包含NMI)。中断门本身是否可以自然地看作原子序列?

2 个答案:

答案 0 :(得分:5)

原子操作在内核中实现如下。在高级别(例如,来自设备驱动程序开发人员的POV),内核提供与用户空间互斥体类似地获取和释放的locks。在较低级别,这些锁使用atomic operations的组合实现,并向preemption should not occur发送内核调度程序。

在调度程序本身中,masking interrupts保证原子性。这是使用单个指令(cli或sti)完成的,因此它本身就是原子的。 NMI确实可以在中断被清除时发生,但是,这是一种特殊情况。 NMI处理程序知道它可以在一个奇怪的上下文中调用,所以它确保它not change the context

答案 1 :(得分:3)

上下文切换和互斥。 我假设通过上下文切换你的意思是任务切换。我必须注意,如果诚实的任务切换没有必要需要锁定。几乎在任何情况下,schedulling本身都需要互相排斥访问制定调度决策所需的一些数据,而不是上下文切换本身,这是遵循制定调度决策。

几乎所有现代操作系统都遵循设计,其中系统中的每个线程保留两个堆栈,一个在用户模式侧,另一个在内核模式侧。完成任务切换所需的全部操作是三步操作:

  1. 将CPU(所有寄存器)的状态保存在离开CPU的任务的堆栈上。
  2. 切换活动堆栈
    1. 将实际堆栈指针保存在当前任务描述符(离开CPU的任务)中
    2. 切换指向当前任务描述符的指针
    3. 从当前任务描述符(到达CPU的任务)将新堆栈指针安装到堆栈指针寄存器中
  3. 从堆栈中恢复CPU的状态(到达CPU的任务的堆栈)。
  4. 正如您所看到的,本质上下文切换操作时使用两组本地任务数据,但不是全局数据。 CPU寄存器的内容不是共享数据的示例。 注意!如果内核中只有一个代码实现上下文切换,则所有上下文切换都将通过相同的代码。并且在它中提供保证,如果任务被切换,该任务的内核堆栈的顶部将包含良好定义的格式的所有CPU状态。每次切换任务时,都可以在内核堆栈的顶部找到CPU状态数据!

    存在两个注释:

    1. 请记住,如果不与其他处理器共享数据,那么CPU上的几乎所有指令都将以原子形式执行。这意味着指令执行不能在中间被中断。因此,可以通过xchg指令和基于寄存器的操作数安全地切换到当前任务描述符的指针。
    2. 可以通过硬件中断来中断任务切换。但是中断处理程序与任务上下文无关。由于这个原因,他们将在任务方面安全地抢占任务切换,并且通过CPU使用通常使用堆栈机设计方法。
    3. 内核中的Mutal排除。 一般来说,kenel可以分为两部分:硬件驱动和软件驱动。 fisrt one包括可由硬件设备(中断处理程序)分配的中断调用的代码,并且它不依赖于当前正在执行的线程,并且不依赖于以某种方式影响执行任务的上下文第二个包括当前正在执行的线程(系统调用和异常处理程序)显式或隐式调用的代码,并且通常需要访问任务描述符数据。

      这两个内核部分为数据锁定提供了不同的要求。仅由内核的软件驱动部分使用的数据可以使用由内核环境提供的众所周知的同步原语。我的意思是遵循检查和等待方法的原语。例如互斥体。如果需要的数据被锁定,任务可以在等待队列中注册self并释放CPU以执行其他任务。

      只能由硬件驱动的部分(仅由一个特定的中断处理程序)使用的数据可以依赖于如果处理相同时间的中断,则下一个中断无法传送到CPU的事实当处理程序通知中断控制器它完成中断处理(所谓的EOI(End Of Interrupt)通知)时。由于这个数据仅由一个中断处理程序使用,并且使用位于中断处理程序执行开始和发送EOI通知之间,以自然方式受到保护,并且不需要任何额外的锁定。

      最后,软件和硬件驱动的内核部件之间或不同优先级中断处理程序之间共享的数据为mutal排除实现提供了最严格的要求。这样的数据既不受检查等待锁也不受同一优先级中断传递的串行性质的保护。对于此类数据的锁定要求有两个主要因素:

      1. 在任意不可预测的时间内,任何执行点上的中断处理程序都可以抢占在CPU上执行的当前活动。换句话说,硬件中断处理是完全异步过程。
      2. 处理程序无法等待资源释放。尝试在中断处理程序中等待资源释放很容易导致中断处理程序等待资源释放之间的死锁,同时阻止软件驱动的内核部分执行,并且任务拥有了resours并被阻止执行作为软件驱动的一部分内核部分释放资源。
      3. 由于这种情况,在这种情况下使用下一个同步技术:

        1. 使用原子操作。注意,在所描述的上下文中,几乎每个CPU指令都可以被认为是原子的。通常通过原子操作的术语,peaple意味着处理器指令以“锁定”为前缀。前缀,但仅在多处理器系统的情况下需要锁定,以防止对同一存储器单元的物理并行访问不一致。
        2. 使用无等待算法和数据结构。
        3. 使用IST代替ISR。这种设计方法假定必须在中断处理程序中完成的唯一工作是安排中断处理线程运行并通知它有关中断的信息。由于这一点,一方面在中断处理程序中运行的代码量和它所需的锁数量大大减少。而另一方面,从ISR移到IST的代码可以使用锁定而没有任何限制。
        4. 最常见,最常见和最流行的方法,攻击中断锁定要求的主要因素之一 - 抢占。可以通过禁用中断接受来防止抢占。 CPU通常支持某种禁用中断的方法(例如x86处理器提供两个特殊指令 - CLI(禁用中断)和STI(启用中断))。如果CPU没有提供这样的功能,通常也可以在中断控制器一侧禁用中断(我相信这是不同RISC处理器的情况)。中断禁用意味着没有一个中断处理程序会抢占受保护的代码段的执行,因为处理器不能从中断控制器接收信号,并且线程切换不会被完成(至少是隐式),因为通常触发调度程序的计时器不能像所有其他设备一样传递中断。这种同步方法对于内核实现肯定是必要的,但是太残酷了。注意!它通过禁用系统中的所有中断来实现同步,这会对中断处理延迟和内核响应性产生负面影响。由于这个内核开发人员试图使这种形式的关键部分尽可能少且尽可能短。
        5. 关于中断门。是的,你是对的。英特尔处理器在进入中断门期间自动禁用中断,并从处理程序重新启用它们。由于这个整个中断处理程序可以被认为是以原子方式执行。但!正如我在上面几行中所描述的那样,人们试图尽量减少以这种方式保护的代码量。因此,即使操作系统内核使用中断门而不是陷阱门,它也会尽可能快地在中断处理程序中手动重新启用中断。

          NMI是一个非常特殊的案例。它的出现通常意味着整个世界都崩溃了。在所有系统都已经停机的那一刻,有人会关心同步吗?