CPU调度的上下文切换(2)

一、什么是CPU调度的上下文切换

上下文切换是指CPU从一个任务切换到另一个任务时,保存当前任务的状态并加载下一个任务的状态的过程。具体步骤如下:

  1. 保存当前任务状态:将当前任务的寄存器、程序计数器、堆栈指针等状态信息保存到内存中。

  2. 加载下一个任务状态:从内存中加载下一个任务的寄存器、程序计数器、堆栈指针等状态信息。

  3. 切换到下一个任务:CPU开始执行下一个任务的指令。

上下文切换在以下情况下发生:

  • 时间片用完:在分时系统中,当一个任务的时间片用完时,操作系统会调度另一个任务运行。

  • 任务阻塞:当一个任务因等待I/O操作或其他资源而阻塞时,操作系统会调度另一个任务运行。

  • 高优先级任务就绪:当一个高优先级任务就绪时,操作系统可能会中断当前任务,调度高优先级任务运行。

上下文切换虽然必要,但会带来一定的开销,因为保存和加载任务状态需要时间和资源。因此,设计高效的调度算法以减少不必要的上下文切换是操作系统优化的重要方面。

二、上下文切换流程

  1. 触发调度

    • 当一个任务的时间片用完、任务阻塞或高优先级任务就绪时,内核会调用schedule()函数来触发调度。

  2. 选择下一个任务

    • schedule()函数会调用pick_next_task()来选择下一个要运行的任务。

  3. 上下文切换

    • 选择好下一个任务后,内核会调用context_switch()函数来进行实际的上下文切换。

上下文切换的具体实现

在Linux内核中,上下文切换的主要代码位于kernel/sched/core.c文件中。以下是context_switch()函数的简化流程:

static __always_inline struct rq *
context_switch(struct rq *rq, struct task_struct *prev,
               struct task_struct *next, struct rq_flags *rf)
{
    struct mm_struct *mm, *oldmm;

    prepare_task_switch(rq, prev, next);

    mm = next->mm;
    oldmm = prev->active_mm;

    if (unlikely(!mm)) {
        next->active_mm = oldmm;
        atomic_inc(&oldmm->mm_count);
        enter_lazy_tlb(oldmm, next);
    } else
        switch_mm_irqs_off(oldmm, mm, next);

    if (unlikely(!prev->mm)) {
        prev->active_mm = NULL;
        rq->prev_mm = oldmm;
    }

    switch_to(prev, next, prev);

    barrier();
    return finish_task_switch(prev);
}

具体保存和加载的数据

  1. 保存当前任务状态

    • 寄存器:包括通用寄存器、浮点寄存器、程序计数器(PC)、堆栈指针(SP)等。

    • 内存管理单元(MMU)状态:包括页表基址寄存器(CR3)等。

    • 其他状态:如浮点单元(FPU)状态、调试寄存器等。

  2. 加载下一个任务状态

    • 寄存器:加载下一个任务的通用寄存器、浮点寄存器、程序计数器(PC)、堆栈指针(SP)等。

    • 内存管理单元(MMU)状态:加载下一个任务的页表基址寄存器(CR3)等。

    • 其他状态:加载下一个任务的浮点单元(FPU)状态、调试寄存器等。

详细步骤

  1. 保存当前任务状态

    • switch_to宏中,当前任务的寄存器状态会被保存到任务的内核栈中。

  2. 切换内存空间

    • switch_mm_irqs_off()函数会切换内存管理单元(MMU)的状态,包括页表基址寄存器(CR3)的更新。

  3. 加载下一个任务状态

    • switch_to宏会从下一个任务的内核栈中加载寄存器状态,并开始执行下一个任务的指令。

代码示例

以下是switch_to宏的简化实现:

#define switch_to(prev, next, last)                    \
do {                                                    \
    ((last) = __switch_to((prev), (next)));             \
} while (0)

__switch_to函数会处理具体的寄存器保存和加载操作。

总结

在Linux内核中,上下文切换是一个复杂但高效的过程,涉及保存当前任务的状态、切换内存空间和加载下一个任务的状态。具体保存和加载的数据包括寄存器、内存管理单元状态和其他相关状态。通过这种方式,Linux内核能够实现多任务的高效调度和执行。

三、通用寄存器、浮点寄存器、程序计数器(PC)、堆栈指针(SP)、MMU、FPU是什么

1. 通用寄存器

  • 位置:CPU内部。

  • 作用:通用寄存器是CPU中最常用的存储单元,用于临时存放数据、地址和中间计算结果。例如,x86架构中的EAXEBXECXEDX等寄存器。

  • 特点:访问速度极快,但数量有限。


2. 浮点寄存器

  • 位置:CPU内部(通常是FPU的一部分)。

  • 作用:专门用于浮点运算的寄存器,存放浮点数(小数)数据。例如,x86架构中的XMMYMM寄存器(用于SIMD指令)。

  • 特点:支持高精度浮点运算,通常与通用寄存器分离。


3. 程序计数器(PC)

  • 位置:CPU内部。

  • 作用:程序计数器存储下一条要执行的指令的地址。CPU根据PC的值从内存中读取指令并执行。

  • 特点:每个任务都有自己的PC值,上下文切换时需要保存和恢复。


4. 堆栈指针(SP)

  • 位置:CPU内部。

  • 作用:堆栈指针指向当前任务的堆栈的顶部。堆栈用于存储函数调用的返回地址、局部变量和临时数据。

  • 特点:每个任务都有自己的堆栈,上下文切换时需要保存和恢复SP的值。


5. MMU(内存管理单元)

  • 位置:CPU内部。

  • 作用:MMU负责虚拟地址到物理地址的转换(通过页表),并提供内存保护机制。

  • 关键寄存器

    • 页表基址寄存器(如x86的CR3):存储当前任务的页表基地址。

    • TLB(转换后备缓冲区):缓存常用的虚拟地址到物理地址的映射,加速地址转换。

  • 特点:上下文切换时需要切换页表基址寄存器(CR3),以确保每个任务有自己的独立地址空间。


6. FPU(浮点单元)

  • 位置:CPU内部。

  • 作用:FPU专门负责浮点运算,包含浮点寄存器和浮点运算指令。

  • 特点:上下文切换时需要保存和恢复FPU的状态(如浮点寄存器的值)。


上下文切换中的数据保存和加载

在上下文切换时,CPU需要保存当前任务的以下状态到内存中:

  • 通用寄存器:保存到任务的内核栈或任务控制块(TCB)中。

  • 浮点寄存器:保存到任务的内核栈或TCB中。

  • 程序计数器(PC):保存到任务的内核栈或TCB中。

  • 堆栈指针(SP):保存到任务的内核栈或TCB中。

  • MMU状态:保存页表基址寄存器(如CR3)的值。

  • FPU状态:保存浮点寄存器和FPU控制寄存器的值。

当切换到下一个任务时,CPU会从内存中加载这些状态到相应的寄存器中。


总结

  • 通用寄存器、浮点寄存器、PC、SP、MMU、FPU都是CPU内部的硬件组件,而不是内存中的器件。

  • 它们的作用是支持CPU的高效运行和任务管理。

  • 在上下文切换时,这些组件的状态需要保存到内存中,并在切换回来时重新加载。

四、任务的内核栈或任务控制块是什么 

1. 任务的内核栈(Kernel Stack)

概念
  • 内核栈是每个任务在内核模式下运行时使用的栈。

  • 每个任务通常有两个栈:

    • 用户栈:用于任务在用户模式下运行时的函数调用和局部变量存储。

    • 内核栈:用于任务在内核模式下运行时的函数调用和局部变量存储。

作用
  • 当任务通过系统调用、中断或异常进入内核模式时,CPU会切换到该任务的内核栈。

  • 内核栈用于存储:

    • 函数调用的返回地址。

    • 局部变量。

    • 任务在内核模式下的上下文信息(如寄存器状态)。

  • 内核栈是任务在内核模式下运行的基础设施。

特点
  • 每个任务都有自己独立的内核栈。

  • 内核栈的大小通常是固定的(例如,Linux中默认的内核栈大小为8KB或16KB)。

  • 内核栈的内容在任务切换时会被保存和恢复。


2. 任务控制块(TCB,Task Control Block)

概念
  • 任务控制块是操作系统内核中用于描述和管理任务的数据结构。

  • 在Linux中,任务控制块通常被称为task_struct,它是内核中表示一个任务的核心数据结构。

作用
  • TCB存储了任务的所有关键信息,包括:

    • 任务状态:任务的运行状态(如运行、就绪、阻塞等)。

    • 任务上下文:任务的寄存器状态(如通用寄存器、程序计数器、堆栈指针等)。

    • 内存管理信息:任务的页表基址(如CR3)、虚拟地址空间等。

    • 调度信息:任务的优先级、调度策略、时间片等。

    • 资源信息:任务打开的文件、信号处理函数、进程间通信(IPC)状态等。

    • 父子关系:任务的父任务、子任务、兄弟任务等。

特点
  • 每个任务都有一个唯一的TCB。

  • TCB是操作系统调度和管理任务的核心数据结构。

  • 在上下文切换时,操作系统会保存当前任务的上下文到其TCB中,并从下一个任务的TCB中加载上下文。


任务的内核栈和TCB的关系

  • 内核栈主要用于任务在内核模式下运行时的临时数据存储(如函数调用、局部变量等)。

  • TCB则用于存储任务的全局状态信息(如任务上下文、调度信息、资源信息等)。

  • 在上下文切换时:

    • 当前任务的寄存器状态会被保存到其TCB中。

    • 当前任务的内核栈会被切换到下一个任务的内核栈。

    • 下一个任务的寄存器状态会从其TCB中加载到CPU中。


代码示例(Linux内核)

在Linux内核中,任务控制块的定义位于include/linux/sched.h文件中。以下是task_struct的简化定义:

struct task_struct {
    // 任务状态
    volatile long state;  // 任务状态(运行、就绪、阻塞等)
    int exit_state;       // 退出状态

    // 任务上下文
    struct thread_struct thread;  // 任务的CPU上下文(寄存器状态等)

    // 调度信息
    int prio;             // 任务优先级
    struct sched_entity se;  // 调度实体

    // 内存管理信息
    struct mm_struct *mm; // 任务的内存描述符(虚拟地址空间)

    // 资源信息
    struct files_struct *files;  // 打开的文件
    struct signal_struct *signal;  // 信号处理

    // 父子关系
    struct task_struct *parent;  // 父任务
    struct list_head children;   // 子任务列表
    struct list_head sibling;    // 兄弟任务列表
};
  • thread_struct:存储任务的CPU上下文(如寄存器状态)。

  • mm_struct:存储任务的内存管理信息(如页表基址)。


上下文切换的具体流程

  1. 保存当前任务状态

    • 将当前任务的寄存器状态保存到其TCB中。

    • 将当前任务的内核栈指针保存到其TCB中。

  2. 切换到下一个任务

    • 从下一个任务的TCB中加载寄存器状态。

    • 切换到下一个任务的内核栈。

  3. 恢复下一个任务状态

    • 从下一个任务的内核栈中恢复运行状态。


总结

  • 任务的内核栈是任务在内核模式下运行时使用的栈,用于存储函数调用和局部变量。

  • 任务任务控制块(TCB)任务控制块是操作系统用于管理任务的核心数据结构,存储了任务的所有关键信息。

  • 在上下文切换时,操作系统会保存当前任务的状态到其TCB中,并加载下一个任务的状态从其TCB中。

你可能感兴趣的:(java,linux,开发语言)