操作系统实验五 基于内核栈切换的进程切换(哈工大李治军)(一)

简介: 操作系统实验五 基于内核栈切换的进程切换(哈工大李治军)(一)

实验5 基于内核栈切换的进程切换


实验目的

深入理解进程和进程切换的概念;

综合应用进程、CPU 管理、PCB、LDT、内核栈、内核态等知识解决实际问题;

开始建立系统认识。


实验内容

现在的 Linux 0.11 采用 TSS 和一条指令就能完成任务切换,虽然简单,但这指令的执行时间却很长,在实现任务切换时大概需要 200 多个时钟周期。


而通过堆栈实现任务切换可能要更快,而且采用堆栈的切换还可以使用指令流水的并行优化技术,同时又使得 CPU 的设计变得简单。所以无论是 Linux 还是 Windows,进程/线程的切换都没有使用 Intel 提供的这种 TSS 切换手段,而都是通过堆栈实现的。


本次实践项目就是将 Linux 0.11 中采用的 TSS 切换部分去掉,取而代之的是基于堆栈的切换程序。具体的说,就是将 Linux 0.11 中的 switch_to 实现去掉,写成一段基于堆栈切换的代码。


本次实验包括如下内容:


编写汇编程序 switch_to:

完成主体框架;

在主体框架下依次完成 PCB 切换、内核栈切换、LDT 切换等;

修改 fork(),由于是基于内核栈的切换,所以进程需要创建出能完成内核栈切换的样子。

修改 PCB,即 task_struct 结构,增加相应的内容域,同时处理由于修改了 task_struct 所造成的影响。

用修改后的 Linux 0.11 仍然可以启动、可以正常使用。


TSS 切换

在现在的 Linux 0.11 中,真正完成进程切换是依靠任务状态段(Task State Segment,简称 TSS)的切换来完成的。


具体的说,在设计“Intel 架构”(即 x86 系统结构)时,每个任务(进程或线程)都对应一个独立的 TSS,TSS 就是内存中的一个结构体,里面包含了几乎所有的 CPU 寄存器的映像。有一个任务寄存器(Task Register,简称 TR)指向当前进程对应的 TSS 结构体,所谓的 TSS 切换就将 CPU 中几乎所有的寄存器都复制到 TR 指向的那个 TSS 结构体中保存起来,同时找到一个目标 TSS,即要切换到的下一个进程对应的 TSS,将其中存放的寄存器映像“扣在” CPU 上,就完成了执行现场的切换,如下图所示。



Intel 架构不仅提供了 TSS 来实现任务切换,而且只要一条指令就能完成这样的切换,即图中的 ljmp 指令。


具体的工作过程是:


(1)首先用 TR 中存取的段选择符在 GDT 表中找到当前 TSS 的内存位置,由于 TSS 是一个段,所以需要用段表中的一个描述符来表示这个段,和在系统启动时论述的内核代码段是一样的,那个段用 GDT 中的某个表项来描述,还记得是哪项吗?是 8 对应的第 1 项。此处的 TSS 也是用 GDT 中的某个表项描述,而 TR 寄存器是用来表示这个段用 GDT 表中的哪一项来描述,所以 TR 和 CS、DS 等寄存器的功能是完全类似的。

(2)找到了当前的 TSS 段(就是一段内存区域)以后,将 CPU 中的寄存器映像存放到这段内存区域中,即拍了一个快照。

(3)存放了当前进程的执行现场以后,接下来要找到目标进程的现场,并将其扣在 CPU 上,找目标 TSS 段的方法也是一样的,因为找段都要从一个描述符表中找,描述 TSS 的描述符放在 GDT 表中,所以找目标 TSS 段也要靠 GDT 表,当然只要给出目标 TSS 段对应的描述符在 GDT 表中存放的位置——段选择子就可以了,仔细想想系统启动时那条著名的 jmpi 0, 8 指令,这个段选择子就放在 ljmp 的参数中,实际上就 jmpi 0, 8 中的 8。

(4)一旦将目标 TSS 中的全部寄存器映像扣在 CPU 上,就相当于切换到了目标进程的执行现场了,因为那里有目标进程停下时的 CS:EIP,所以此时就开始从目标进程停下时的那个 CS:EIP 处开始执行,现在目标进程就变成了当前进程,所以 TR 需要修改为目标 TSS 段在 GDT 表中的段描述符所在的位置,因为 TR 总是指向当前 TSS 段的段描述符所在的位置。

上面给出的这些工作都是一句长跳转指令 ljmp 段选择子:段内偏移,在段选择子指向的段描述符是 TSS 段时 CPU 解释执行的结果,所以基于 TSS 进行进程/线程切换的 switch_to 实际上就是一句 ljmp 指令:

#define switch_to(n) {
    struct{long a,b;} tmp;
    __asm__(
        "movw %%dx,%1"
        "ljmp %0" ::"m"(*&tmp.a), "m"(*&tmp.b), "d"(TSS(n)
    )
 }
#define FIRST_TSS_ENTRY 4
#define TSS(n) (((unsigned long) n) << 4) + (FIRST_TSS_ENTRY << 3))


GDT 表的结构如下图所示,所以第一个 TSS 表项,即 0 号进程的 TSS 表项在第 4 个位置上,4<<3,即 4 * 8,相当于 TSS 在 GDT 表中开始的位置,TSS(n)找到的是进程 n 的 TSS 位置,所以还要再加上 n<<4,即 n * 16,因为每个进程对应有 1 个 TSS 和 1 个 LDT,每个描述符的长度都是 8 个字节,所以是乘以 16,其中 LDT 的作用就是上面论述的那个映射表,关于这个表的详细论述要等到内存管理一章。TSS(n) = n * 16 + 4 * 8,得到就是进程 n(切换到的目标进程)的 TSS 选择子,将这个值放到 dx 寄存器中,并且又放置到结构体 tmp 中 32 位长整数 b 的前 16 位,现在 64 位 tmp 中的内容是前 32 位为空,这个 32 位数字是段内偏移,就是 jmpi 0, 8 中的 0;接下来的 16 位是 n * 16 + 4 * 8,这个数字是段选择子,就是 jmpi 0, 8 中的 8,再接下来的 16 位也为空。所以 swith_to 的核心实际上就是 ljmp 空, n*16+4*8,现在和前面给出的基于 TSS 的进程切换联系在一起了。



本次实验的内容

虽然用一条指令就能完成任务切换,但这指令的执行时间却很长,这条 ljmp 指令在实现任务切换时大概需要 200 多个时钟周期。而通过堆栈实现任务切换可能要更快,而且采用堆栈的切换还可以使用指令流水的并行优化技术,同时又使得 CPU 的设计变得简单。所以无论是 Linux 还是 Windows,进程/线程的切换都没有使用 Intel 提供的这种 TSS 切换手段,而都是通过堆栈实现的。


本次实践项目就是将 Linux 0.11 中采用的 TSS 切换部分去掉,取而代之的是基于堆栈的切换程序。具体的说,就是将 Linux 0.11 中的 switch_to 实现去掉,写成一段基于堆栈切换的代码。


在现在的 Linux 0.11 中,真正完成进程切换是依靠任务状态段(Task State Segment,简称 TSS)的切换来完成的。具体的说,在设计“Intel 架构”(即 x86 系统结构)时,每个任务(进程或线程)都对应一个独立的 TSS,TSS 就是内存中的一个结构体,里面包含了几乎所有的 CPU 寄存器的映像。有一个任务寄存器(Task Register,简称 TR)指向当前进程对应的 TSS 结构体,所谓的 TSS 切换就将 CPU 中几乎所有的寄存器都复制到 TR 指向的那个 TSS 结构体中保存起来,同时找到一个目标 TSS,即要切换到的下一个进程对应的 TSS,将其中存放的寄存器映像“扣在”CPU 上,就完成了执行现场的切换。


要实现基于内核栈的任务切换,主要完成如下三件工作:


(1)重写 switch_to;

(2)将重写的 switch_to 和 schedule() 函数接在一起;

(3)修改现在的 fork()。


正式修改代码前小结

下面开始正式修改代码。其实主要就修改3个文件,但我不会按照每个文件一次性修改的顺序,而是按照实验逻辑,跳转修改,请保持清晰的逻辑。在截图的右下角有当前位置所在行数,注意观察不要修改错位置。

目录
相关文章
|
3天前
|
消息中间件 算法 调度
深入理解操作系统之进程管理
【5月更文挑战第20天】 随着计算机技术的不断发展,操作系统在计算机系统中扮演着越来越重要的角色。本文将深入探讨操作系统的核心组件之一——进程管理。通过对进程的概念、状态、调度等方面的分析,帮助读者更好地理解操作系统的工作原理和机制。
|
3天前
|
机器学习/深度学习 负载均衡 算法
深入理解操作系统中的进程调度策略
【5月更文挑战第20天】 操作系统的核心职能之一是有效地管理计算机资源,确保系统运行的高效性与稳定性。在多任务环境中,进程调度策略扮演着至关重要的角色,它决定了哪个进程将在何时获得CPU的使用权。本文将探讨现代操作系统中实现的几种经典进程调度算法,并分析它们的设计原理、性能特点以及适用场景。通过对先来先服务(FCFS)、短作业优先(SJF)和轮转调度(RR)等策略的剖析,我们将揭示它们对系统响应时间、吞吐量及公平性的不同影响。进一步地,文章还将讨论如何根据实际需求对这些策略进行优化组合,以期达到最佳的调度效率。
|
3天前
|
负载均衡 算法 Linux
深入理解操作系统中的进程调度策略
【5月更文挑战第20天】 在多任务操作系统中,进程调度策略是核心组件之一,负责决定哪些可运行的进程将获得CPU资源。本文旨在探讨不同的进程调度算法,包括它们的原理、优势、局限性以及在实际操作系统中的应用情况。我们将从简单的先来先服务(FCFS)和短作业优先(SJF)策略讲起,逐步深入到更复杂的轮转(Round Robin)和多级反馈队列(Multilevel Feedback Queue)策略。文章的目的是为读者提供一个清晰的框架,以理解进程调度对系统性能的影响,并分析如何根据不同场景选择合适的调度策略。
|
1天前
|
存储 缓存 Linux
【Linux】进程概念(冯诺依曼体系结构、操作系统、进程)-- 详解
【Linux】进程概念(冯诺依曼体系结构、操作系统、进程)-- 详解
|
2天前
|
负载均衡 算法 调度
深入理解操作系统:进程管理与调度策略
在现代操作系统中,进程管理是核心功能之一,它关系到系统资源的合理分配、任务的有效执行以及用户体验的流畅度。本文将探讨操作系统中进程的概念、进程的状态转换、调度算法及其对系统性能的影响。通过分析不同的进程调度策略,我们旨在揭示它们如何适应多样化的使用场景并平衡效率与公平性。文章还将讨论实时系统中的调度问题以及多核处理器环境下的进程管理挑战。
|
2天前
|
算法 安全 调度
深入理解操作系统:进程管理的艺术
【5月更文挑战第21天】 在现代计算机系统中,操作系统扮演着核心角色,它负责协调和管理硬件资源,为应用程序提供运行环境。本文将深入探讨操作系统中一个关键组成部分——进程管理。我们将从进程的概念出发,逐步解析进程的生命周期、调度策略以及与并发性能的关系。特别地,文章将重点分析如何通过高效的进程调度算法优化系统性能,并讨论现代操作系统中面临的挑战和可能的解决策略。
|
2天前
|
算法 Linux 调度
深入理解操作系统:进程管理与调度策略
【5月更文挑战第21天】 在现代计算机系统中,操作系统的核心职能之一是确保系统资源的有效分配与利用。其中,进程管理作为操作系统的重要组成部分,负责协调和管理运行中的程序。本文将深入探讨进程的概念、状态转换以及进程调度的关键策略,并分析它们如何影响系统性能和用户体验。通过比较不同的进程调度算法,本文旨在提供一个全面的视角,帮助读者更好地理解操作系统内部的运作机制。
|
8天前
|
存储 Linux Shell
Linux:进程等待 & 进程替换
Linux:进程等待 & 进程替换
32 9
|
8天前
|
存储 Linux C语言
Linux:进程创建 & 进程终止
Linux:进程创建 & 进程终止
33 6
|
1天前
|
算法 Linux 调度
【进程调度】Linux内核的进程调度队列--runqueue
【进程调度】Linux内核的进程调度队列--runqueue