2

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

4

2 回答 2

6

原子操作在内核中实现如下。在高层(例如,来自设备驱动程序开发人员的 POV),内核提供类似于用户空间互斥锁的获取和释放锁。在较低级别,这些锁是使用原子操作的组合实现的,并向内核调度程序发出不应发生抢占的信号。

在调度程序本身中,原子性是通过屏蔽中断来保证的。这是使用单个指令(cli 或 sti)完成的,因此它本身是原子的。NMI 确实可以在中断被清除时发生,但是,这是一种特殊情况。NMI 处理程序知道它可以在奇怪的上下文中调用,因此它确保它不会更改上下文

于 2012-05-07T07:03:24.990 回答
3

上下文切换和互斥。我假设上下文切换是指任务切换。我必须注意,如果说实话,任务切换不需要锁定。几乎在任何情况下,调度本身都需要互斥访问做出调度决策所需的某些数据,而不是上下文切换本身,即遵循做出调度决策。

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

  1. 将 CPU 的状态(所有寄存器)保存在离开 CPU 的任务的堆栈上。
  2. 切换活动堆栈
    1. 将实际堆栈指针保存在当前任务描述符中(离开 CPU 的任务)
    2. 切换指向当前任务描述符的指针
    3. 从当前任务描述符(到达 CPU 的任务)中将新的堆栈指​​针安装到堆栈指针寄存器中
  3. 从堆栈(到达 CPU 的任务的堆栈)中恢复 CPU 的状态。

如您所见,从本质上讲,上下文切换使用任务本地的两组数据进行操作,但不是全局的。CPU 寄存器的内容不是共享数据的示例。笔记!如果内核中只有一个代码实现上下文切换,那么所有上下文切换都将通过相同的代码。反过来,这提供了保证,如果任务被切换,该任务的内核堆栈顶部将包含所有定义良好格式的 CPU 状态。并且每次切换任务时,它都能在其内核堆栈的顶部找到 CPU 状态数据!

存在两个注释:

  1. 请记住,如果 CPU 上的几乎所有指令不与其他处理器共享数据,它们都会以原子方式执行。这意味着指令执行不能在中间中断。因此,指向当前任务描述符的切换指针可以通过带有基于寄存器的操作数的 xchg 指令安全地生成。
  2. 来自硬件的中断可以中断任务切换。但是中断处理程序是任务上下文无关的。因此,它们将通过 CPU 使用通常使用堆栈机设计方法来安全地抢占任务切换关于任务和关于自身的任务。

内核中的互斥。一般来说,kenel可以分为硬件驱动和软件驱动两部分。第一个包括可以由硬件设备(中断处理程序)触发的中断调用的代码,它不依赖于当前执行的线程,也不依赖于以某种方式影响当前执行任务的上下文,第二个一种包括由当前正在执行的线程(系统调用和异常处理程序)显式或隐式调用的代码,并且通常需要访问任务描述符数据。

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

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

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

  1. 在 CPU 上执行的当前活动可以在任意不可预知的时间在任何执行点被中断处理程序抢占。所以换句话说,硬件中断处理是完全异步的过程。
  2. 处理程序不能等待资源释放。尝试在中断处理程序中等待资源释放很容易导致中断处理程序等待资源释放与同时阻塞软件驱动的内核部分执行之间的死锁,并且任务拥有资源并被阻塞作为软件驱动的一部分执行内核部分释放资源。

因此,在这种情况下使用下一个同步技术:

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

关于中断门。是的你是对的。英特尔处理器在进入中断门期间自动禁用中断,并在处理程序的 iret 上重新启用它们。因此,整个中断处理程序可以被视为以原子方式执行。但!正如我在上面几行中所描述的,人们试图以这种方式最小化受保护的代码量。结果,即使操作系统内核使用中断门而不是陷阱门,它也会尽可能快地尝试在中断处理程序中手动重新启用中断。

NMI 是一个非常特殊的情况。它的发生通常意味着整个世界都崩溃了。当所有系统都已经关闭时,是否有人会关心同步?

于 2012-12-01T09:29:11.753 回答