实验题目:

实现基于内核栈切换的进程切换

实验目的和要求:
构建出内核栈,要在适当的地址压入适当的返回地址,并根据内核栈的样子,编写相应的汇编代码,精细地完成内核栈的入栈和出栈操作,在适当的地方弹出正确的返回地址,以保证能顺利完成进程的切换。同时,还要完成内核栈和 PCB 的关联,在 PCB 切换时,完成内核栈的切换。

实验过程:
本次实践项目就是将Linux 0.11中采用的TSS切换部分去掉,取而代之的是基于堆栈的切换程序。具体的说,就是将Linux 0.11中的switch_to实现去掉,写成一段基于堆栈切换的代码。
本次实验包括如下内容:
编写汇编程序switch_to:
完成主体框架;
在主体框架下依次完成PCB切换、内核栈切换、LDT切换等;
修改fork(),由于是基于内核栈的切换,所以进程需要创建出能完
成内核栈切换的样子。
修改PCB,即task_struct结构,增加相应的内容域,同时处理由于修改了task_struct所造成的影响。

1、找到当前进程的PCB和新进程的PCB

当前进程的PCB是用一个全局变量current指向的(在sched.c中定义) ,所以current即指向当前进程的PCB
为了得到新进程的PCB,我们需要对schedule()函数做如下修改:

if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
c = (*p)->counter, next = i;

switch_to(next);
修改为:
if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
c = (*p)->counter, next = i, pnext = *p;

switch_to(pnext, LDT(next));
这样,pnext就指向下个进程的PCB

2.修改switch_to

将 Linux 0.11 中原有的 switch_to 实现去掉,写成一段基于栈切换的代码。由于要对
内核进行精细的操作,所以需要用汇编代码来实现 switch_to 的编写,既然要用汇编实
现 switch_to,那么将 switch_to 的实现放在 system_call.s 中是最合适的。这个函数
依次主要完成如下功能:由于是 C 语言调用汇编,所以需要首先在汇编中处理栈帧,即
处理 ebp 寄存器;接下来要取出表示下一个进程 PCB 的参数,并和 current 做一个比较,
如果等于 current,则什么也不用做;如果不等于 current,就开始进程切换,依次完
成 PCB 的切换、TSS 中的内核栈指针的重写、内核栈的切换、LDT 的切换以及 PC 指针(即
CS:EIP)的切换
switch_to:
pushl %ebp
movl %esp,%ebp
pushl %ecx
pushl %ebx
pushl %eax

(1)判断要切换的进程和当前进程是否是同一个进程

movl 8(%ebp),%ebx    /*%ebp+8就是从右往左数起第二个参数,也就是*pnext*/
cmpl %ebx,current   /* 如果当前进程和要切换的进程是同一个进程,就不切换了 */
je 1f
/*先得到目标进程的pcb,然后进行判断
如果目标进程的pcb(存放在ebp寄存器中) 等于   当前进程的pcb => 不需要进行切换,直接退出函数调用
如果目标进程的pcb(存放在ebp寄存器中) 不等于 当前进程的pcb => 需要进行切换,直接跳到下面去执行*/

(2)切换PCB

movl %ebx,%eax
xchgl %eax,current
/*ebx是下一个进程的PCB首地址,current是当前进程PCB首地址*/

(3)TSS中的内核栈指针的重写

movl tss,%ecx        /*%ecx里面存的是tss段的首地址,在后面我们会知道,tss段的首地址就是进程0的tss的首地址,
根据这个tss段里面的内核栈指针找到内核栈,所以在切换时就要更新这个内核栈指针。也就是说,
任何正在运行的进程内核栈都被进程0的tss段里的某个指针指向,我们把该指针叫做内核栈指针。*/
addl $4096,%ebx           /* 未加4KB前,ebx指向下一个进程的PCB首地址,加4096后,相当于为该进程开辟了一个“进程页”,ebx此时指向进程页的最高地址*/
movl %ebx,ESP0(%ecx)        /* 将内核栈底指针放进tss段的偏移为ESP0(=4)的地方,作为寻找当前进程的内核栈的依据*/
/* 由上面一段代码可以知道们的“进程页”是这样的,PCB由低地址向上扩展,栈由上向下扩展。
也可以这样理解,一个进程页就是PCB,我们把内核栈放在最高地址,其它的task_struct从最低地址开始扩展*/

(4)切换内核栈

# KERNEL_STACK代表kernel_stack在PCB表的偏移量,意思是说kernel_stack位于PCB表的第KERNEL_STACK个字节处,注意:PCB表就是task_struct
movl %esp,KERNEL_STACK(%eax)    /* eax就是上个进程的PCB首地址,这句话是将当前的esp压入旧PCB的kernel_stack。所以该句就是保存旧进程内核栈的操作。*/
movl 8(%ebp),%ebx       /*%ebp+8就是从左往右数起第一个参数,也就是ebx=*pnext ,pnext就是下一个进程的PCB首地址。至于为什么是8,请查看附录(I)*/
movl KERNEL_STACK(%ebx),%esp    /*将下一个进程的内核栈指针加载到esp*/

(5)切换LDT

movl 12(%ebp),%ecx         /* %ebp+12就是从左往右数起第二个参数,对应_LDT(next) */
lldt %cx                /*用新任务的LDT修改LDTR寄存器*/
下一个进程在执行用户态程序时使用的映射表就是自己的 LDT 表了,地址空间实现了分离

(6)重置一下用户态内存空间指针的选择符fs

movl $0x17,%ecx
mov %cx,%fs
通过 fs 访问进程的用户态内存,LDT 切换完成就意味着切换了分配给进程的用户态内存地址空间,
所以前一个 fs 指向的是上一个进程的用户态内存,而现在需要执行下一个进程的用户态内存,
所以就需要用这两条指令来重取 fs。

3.修改fork
对于得到CPU的新的进程,我们就是要把进程的用户栈、用户程序和其内核栈通过压在内核栈中的 SS:ESP,CS:EIP 关联在一起。
另外,由于 fork() 这个叉子的含义就是要让父子进程共用同一个代码、数据和堆栈,所以修改 fork() 的核心工作就是要形成如下图所示的子进程内核栈结构。
对 fork() 的修改就是对子进程的内核栈的初始化,在 fork() 的核心实现 *copy_process 中,p = (struct task_struct *) get_free_page();用来完成申请一页内存作为子进程的 PCB,而 p 指针加上页面大小就是子进程的内核栈位置,所以语句 krnstack = (long *) (PAGE_SIZE + (long) p); 就可以找到子进程的内核栈位置,*接下来就是初始化 krnstack (内核栈)中的内容了。
*(–krnstack) = ss & 0xffff;
*(–krnstack) = esp;
*(–krnstack) = eflags;
*(–krnstack) = cs & 0xffff;
*(–krnstack) = eip;
这五条语句就完成了上图所示的那个重要的关联,因为其中 ss,esp 等内容都是 copy_proces() 函数的参数,这些参数来自调用 copy_proces() 的进程的用户栈中,就是父进程的用户栈中。

对 krnstack 进行初始化:
*(–krnstack) = ebp;
*(–krnstack) = ecx;
*(–krnstack) = ebx;
// 这里的 0 最有意思,代表返回值是0,与父进程区分
*(–krnstack) = 0;

四次弹栈以及 ret 处理使用1: popl %eax
popl %ebx
popl %ecx
popl %ebp
Ret

switch_to()中的 ret 指令,这条指令要从内核栈中弹出一个 32 位数作为 EIP 跳去执行,所以需要弄一个函数地址(仍然是一段汇编程序,所以这个地址是这段汇编程序开始处的标号)并将其初始化到栈中。搞一个名为 first_return_from_kernel 的汇编标号,然后可以用语句 *(–krnstack) = (long) first_return_from_kernel; 将这个地址初始化到子进程的内核栈中,执行 ret 以后就会跳转到 first_return_from_kernel 去执行了。

4.编写first_return_from_kernel
PCB 切换完成、内核栈切换完成、LDT 切换完成之后需要完成用户栈和用户代码的切换,依靠的核心指令就是 iret,当然在切换之前应该恢复一下执行现场,主要就是eax,ebx,ecx,edx,esi,edi,gs,fs,es,ds 等寄存器的恢复。

first_return_from_kernel 的核心代码:

popl %edx
popl %edi
popl %esi
pop %gs
pop %fs
pop %es
pop %ds
Iret

最后,注意由于switch_to()和first_return_from_kernel都是在system_call.s中实现的,要想在schedule.c和fork.c中调用它们,就必须在system_call.s中将这两个标号声明为全局的,同时在引用到它们的.c文件中声明它们是一个外部变量。
system_call.s中的全局声明

.globl switch_to
.globl first_return_from_kernel

对应.c文件中的外部变量声明:

extern long switch_to;
extern long first_return_from_kernel;

实现基于内核栈切换的进程切换-linux011相关推荐

  1. 在Linux-0.11中实现基于内核栈切换的进程切换

    原有的基于TSS的任务切换的不足 进程切换的六段论 1 中断进入内核 2 找到当前进程的PCB和新进程的PCB 3 完成PCB的切换 4 根据PCB完成内核栈的切换 5 切换运行资源LDT 6 利用I ...

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

    实验5 基于内核栈切换的进程切换 实验目的 深入理解进程和进程切换的概念: 综合应用进程.CPU 管理.PCB.LDT.内核栈.内核态等知识解决实际问题: 开始建立系统认识. 实验内容 现在的 Lin ...

  3. 哈工大-基于内核栈切换的进程切换

    1. 课程说明 难度系数:★★★★☆ 本实验是 操作系统之进程与线程 - 网易云课堂 的配套实验,推荐大家进行实验之前先学习相关课程: L10 用户级线程 L11 内核级线程 L12 核心级线程实现实 ...

  4. 哈工大操作系统实验4---基于内核栈切换的进程切换

    前置知识 关于栈桢 关于栈栈帧详解https://blog.csdn.net/ylyuanlu/article/details/18947951 进程切换流程 首先先了解一下进程切换的流程.开始的时候 ...

  5. 基于 mykernel 实现具有进程切换的基本内核

    由SA***411创作 实验环境准备 目标实验环境 实验平台: Windows Hyper-V 虚拟机平台 实验 OS: Ubuntu 18.04 Linux 内核版本: Linux 3.9.4 my ...

  6. 20135202闫佳歆--week 8 实验:理解进程调度时机跟踪分析进程调度与进程切换的过程--实验及总结...

    week 8 实验:理解进程调度时机跟踪分析进程调度与进程切换的过程 1.环境搭建: rm menu -rf git clone https://github.com/megnning/menu.gi ...

  7. 操作系统实验4:基于内核栈完成进程切换

    一.参考 <操作系统原理.实现与实践>李治军.刘宏伟编著 二.实验目标 Linux0.11中进程切换是依靠任务状态段(task struct segment,TSS)的切换来实现的,本实践 ...

  8. 某系统采用基于优先权的非抢占式进程调度策略,完成一次进程调度和进程切换的系统时间开销为 1μs。

    某系统采用基于优先权的非抢占式进程调度策略,完成一次进程调度和进程切换的系统时间开销为 1μs.在 T 时刻就绪队列中有 3 个进程 P1.P2 和 P3,其在就绪队列中的等待时间.需要的 CPU 时 ...

  9. 理解进程创建、可执行文件的加载和进程执行进程切换,重点理解分析fork、execve和进程切换

    学号:384 原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/ 实验目标 1.分析fork函数对应的内核处理过程do_fork,理解创建一个 ...

最新文章

  1. 在ubuntu 11.10 64位 上安装adobe flash player
  2. 2017可以兼容那些jdk_2019从头跃——核心基础:何为JDK?Java开发工具包须知总览...
  3. NLP:Transformer的简介(优缺点)、架构详解之详细攻略
  4. Tomcat权威指南-读书摘要系列6
  5. bme280 环境传感器开发板_半导体所在柔性湿度传感器与非接触控制方面取得进展...
  6. Spring-context-ConfigurationClassUtils类
  7. sql server 运维时CPU,内存,操作系统等信息查询(用sql语句)
  8. FireFox2和FireFox3共存解决方案(附完整图解)
  9. DICOM VR数据类型表
  10. vega56刷64_vega56刷vega64_vega56和1070ti_vega56功耗-太平洋电脑网
  11. user-modify
  12. 卸载重装Ubuntu22.04双系统
  13. 外文书籍的中文翻译版本作参考文献,文献引用格式
  14. itextpdf将带复选框的html_使用flying-saucer 实现 html转pdf实现input框select,textarea checkbox等的显示...
  15. Opencv-Python-导向滤波快速导向滤波
  16. ucenter应用通信过程
  17. save-ps-to-svg1.0百度网盘资源
  18. Sparse Matrix, MUMPS
  19. 记事本打开文件乱码的问题
  20. 筛查肌肉病变,首选磁共振

热门文章

  1. Ceres Solver: 高效的非线性优化库(二)实战篇
  2. css元素穿透。 pointer-events: none;
  3. Thrift-java实例
  4. hdu-3488-Tour(KM最佳完美匹配)
  5. String类型转换的三种方法分析
  6. 解决-硬盘安装器/GHOSTERR/WINPE/FreeLaunchBar问题
  7. pandas知识点(汇总和计算描述统计)
  8. django的配置文件字符串是怎么导入的?
  9. 数据库和缓存一致性分析
  10. bootstrap-datepicker 开始时间-结束时间 thinkphp