【问题标题】:How to keep atomicity when contex switching in the kernel mode?内核模式下上下文切换时如何保持原子性?
【发布时间】:2012-05-15 16:42:02
【问题描述】:

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

【问题讨论】:

  • 您不必自己处理 Linux 驱动程序中的上下文切换。它通过标准内核例程发生。还是您的问题更抽象,与 Linux 内核没有直接关系?
  • 是的,我想知道linux内核的实现
  • 任何时候都不会发生上下文切换。中断例程有下半部分和上半部分。 en.wikipedia.org/wiki/Interrupt_handler

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


【解决方案1】:

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

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

【讨论】:

  • 中断门本身是否屏蔽了所有中断??
  • 在 x86 上,CLI instruction 掩码是除 NMI 之外的中断。
【解决方案2】:

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-17
    • 1970-01-01
    • 1970-01-01
    • 2017-05-12
    • 2019-05-09
    • 1970-01-01
    相关资源
    最近更新 更多