当前位置: 首页 > news >正文

政府网站开发的目的com天堂网

政府网站开发的目的,com天堂网,锐狐 网站 后台,醴陵网站开发实验内容 本次实践项目就是将 Linux 0.11 中采用的 TSS 切换部分去掉,取而代之的是基于堆栈的切换程序。具体的说,就是将 Linux 0.11 中的 switch_to 实现去掉,写成一段基于堆栈切换的代码。 本次实验包括如下内容: 编写汇编程…

实验内容

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

本次实验包括如下内容:

  • 编写汇编程序 switch_to
  • 完成主体框架;
  • 在主体框架下依次完成 PCB 切换、内核栈切换、LDT 切换等;
  • 修改 fork(),由于是基于内核栈的切换,所以进程需要创建出能完成内核栈切换的样子。
  • 修改 PCB,即 task_struct 结构,增加相应的内容域,同时处理由于修改了 task_struct 所造成的影响。
  • 用修改后的 Linux 0.11 仍然可以启动、可以正常使用。
  • (选做)分析实验 3 的日志体会修改前后系统运行的差别。

具体的说,就是将 Linux 0.11 中的 switch_to 实现去掉,写成一段基于堆栈切换的代码。

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

  • (1)重写 switch_to
  • (2)将重写的 switch_toschedule() 函数接在一起;
  • (3)修改现在的 fork()

实验步骤

1.修改schedule与switch_to

  • 先来了解一下linux0.11下是如何实现内核栈切换的

目前 Linux 0.11 中工作的 schedule() 函数是首先找到下一个进程的数组位置 next,而这个 next 就是 GDT 中的 n,所以这个 next 是用来找到切换后目标 TSS 段的段描述符的,一旦获得了这个 next 值,直接调用上面剖析的那个宏展开 switch_to(next);就能完成如图 TSS 切换所示的切换了。

在这里插入图片描述
在这里插入图片描述
但是本实验要求用栈切换
思考:为什么能用栈切换呢?进程切换其实就是修改当前PCB,下一个进程的PCB,当前进程的内核栈,下一个进程的内核栈等信息,这些信息保存在tss中,其实这些信息也可以保存在内核栈,这样就直接可以用栈实现切换了

虽然 TSS(next)不再需要了,但是 LDT(next)仍然是需要的,也就是说,现在每个进程不用有自己的 TSS 了,因为已经不采用 TSS 进程切换了,但是每个进程需要有自己的 LDT,地址分离地址还是必须要有的,而进程切换必然要涉及到 LDT 的切换。

  • 修改schedule

综上所述,需要将目前的 schedule() 函数(在 kernal/sched.c 中)做稍许修改,即将下面的代码:

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,所以需要定义pnext并初始化

struct tast_struct *pnext = &(init_task.task);

位置如下
在这里插入图片描述

  • 实现switch_to
    思考:switch_to完成的功能是什么呢?
    linux0.11中的switch_to是通过tss来实现,tss实现的是?tss中保存了各种寄存器的值,在进程交换的时候,把CPU的各种寄存器的值拍到原tss中,然后将新的tss里面的值扣到CPU各个寄存器里。现在我们不借助tss,直接用内核栈来实现,其实完成的切换内容差不多是一样的
    由于是 C 语言调用汇编,所以需要首先在汇编中处理栈帧,即处理 ebp 寄存器;接下来要取出表示下一个进程 PCB 的参数,并和 current 做一个比较,如果等于 current,则什么也不用做;如果不等于 current,就开始进程切换,依次完成 PCB 的切换TSS 中的内核栈指针的重写内核栈的切换LDT 的切换以及 PC 指针(即 CS:EIP)的切换

由于要对内核栈进行精细的操作,所以需要用汇编代码来完成函数 switch_to 的编写。,而switch_to函数是一个系统调用,故写在/kernel/system_call.s中

.align 2
switch_to://因为该汇编函数要在c语言中调用,所以要先在汇编中处理栈帧pushl %ebpmovl %esp,%ebppushl %ecxpushl %ebxpushl %eax//先得到目标进程的pcb,然后进行判断//如果目标进程的pcb(存放在ebp寄存器中) 等于   当前进程的pcb => 不需要进行切换,直接退出函数调用//如果目标进程的pcb(存放在ebp寄存器中) 不等于 当前进程的pcb => 需要进行切换,直接跳到下面去执行movl 8(%ebp),%ebxcmpl %ebx,currentje 1f/** 执行到此处,就要进行真正的基于堆栈的进程切换了 */// PCB的切换movl %ebx,%eaxxchgl %eax,current// TSS中内核栈指针的重写movl tss,%ecxaddl $4096,%ebxmovl %ebx,ESP0(%ecx)//切换内核栈movl %esp,KERNEL_STACK(%eax)movl 8(%ebp),%ebxmovl KERNEL_STACK(%ebx),%esp//LDT的切换movl 12(%ebp),%ecxlldt %cxmovl $0x17,%ecxmov %cx,%fscmpl %eax,last_task_used_mathjne 1fclts//在到子进程的内核栈开始工作了,接下来做的四次弹栈以及ret处理使用的都是子进程内核栈中的东西1:	popl %eaxpopl %ebxpopl %ecxpopl %ebpret

位置图如下
在这里插入图片描述

下面来解释一下各部分交换如何实现的:

思考:在执行switch_to之前,栈里应该是什么样子呢?
在这里插入图片描述

	movl 8(%ebp),%ebxcmpl %ebx,current

所以movl 8(%ebp),%ebx,就是将pnext的值赋值给ebx,也即是下一个PCB的参数,然后cmpl %ebp,current即是比较两者是否相等

PCB 的切换
完成 PCB 的切换可以采用下面两条指令,其中 ebx 是从参数中取出来的下一个进程的 PCB 指针,

movl %ebx,%eax
xchgl %eax,current

经过这两条指令以后,eax 指向现在的当前进程,ebx 指向下一个进程,全局变量 current 也指向下一个进程。

TSS 中的内核栈指针的重写

TSS 中的内核栈指针的重写可以用下面三条指令完成,其中宏ESP0 = 4,struct tss_struct *tss = &(init_task.task.tss); 也是定义了一个全局变量,和 current 类似,用来指向那一段 0 号进程的 TSS 内存。
sched.c中定义struct tss_struct *tss=&(init_task.task.tss)这样一个全局变量

struct tss_struct *tss = &(init_task.task.tss)

位置图
在这里插入图片描述

现在虽然不使用 TSS 进行任务切换了,但是 Intel 的这态中断处理机制还要保持,所以仍然需要有一个当前 TSS,这个 TSS 就是我们定义的那个全局变量 tss,即 0 号进程的 tss,所有进程都共用这个 tss,任务切换时不再发生变化。

movl tss,%ecx
addl $4096,%ebx
movl %ebx,ESP0(%ecx)

定义 ESP0 = 4 是因为 TSS 中内核栈指针 esp0 就放在偏移为 4 的地方,看一看 tss 的结构体定义就明白了
内核栈的切换
将寄存器 esp(内核栈使用到当前情况时的栈顶位置)的值保存到当前PCB中,再从下一个 PCB 中的对应位置上取出保存的内核栈栈顶放入esp 寄存器,这样处理完以后,再使用内核栈时使用的就是下一个进程的内核栈了。
Linux 0.11的PCB定义中没有保存内核栈指针这个域(kernelstack),所以需要我们额外添加。在(/oslab/linux0.11/include/linux/sched.h)中找到结构体task_struct的定义,对其进行如下修改:

include/linux/sched.h 文件

long kernelstack;

位置图

在这里插入图片描述

由于这里将 PCB 结构体的定义改变了,所以在产生 0 号进程的 PCB 初始化时也要跟着一起变化, 需要修改 #define INIT_TASK,即在 PCB 的第四项中增加关于内核栈栈指针的初始化

#define INIT_TASK \
/* state etc */	{ 0,15,15,\
/* signals */	0,{{},},0, \
...
改为:
#define INIT_TASK \
/* state etc */	{ 0,15,15, PAGE_SIZE+(long)&init_task,\
/* signals */	0,{{},},0, \
...,

同时在(system_call.s)中定义KERNEL_STACK = 12 并且修改汇编硬编码,修改代码如下:

ESP0 = 4
KERNEL_STACK = 12state   = 0     # these are offsets into the task-struct.
counter = 4
priority = 8
kernelstack = 12
signal  = 16
sigaction = 20      # MUST be 16 (=len of sigaction)
blocked = (37*16)

LDT 的切换
指令 movl 12(%ebp),%ecx 负责取出对应 LDT(next)的那个参数,指令 lldt %cx负责修改LDTR 寄存器,一旦完成了修改,下一个进程在执行用户态程序时使用的映射表就是自己的LDT表了,地址空间实现了分离。

	movl 12(%ebp),%ecxlldt %cxmovl $0x17,%ecxmov %cx,%fsmovl 12(%ebp),%ecxlldt %cx

switch_to 代码中在切换完 LDT 后的两句,即:

! 切换 LDT 之后
movl $0x17,%ecx
mov %cx,%fs

这两句代码的含义是重新取一下段寄存器 fs 的值,这两句话必须要加、也必须要出现在切换完 LDT 之后,这是因为在实践项目 2 中曾经看到过 fs 的作用——通过 fs 访问进程的用户态内存,LDT 切换完成就意味着切换了分配给进程的用户态内存地址空间,所以前一个 fs 指向的是上一个进程的用户态内存,而现在需要执行下一个进程的用户态内存,所以就需要用这两条指令来重取 fs。

PC 指针(即 CS:EIP)的切换
关于 PC 的切换,和前面论述的一致,依靠的就是 switch_to的最后一句指令ret,虽然简单,但背后发生的事却很多:schedule() 函数的最后调用了这个 switch_to 函数,所以这句指令ret 就返回到下一个进程(目标进程)的 schedule()函数的末尾,遇到的是},继续ret回到调用的 schedule() 地方,是在中断处理中调用的,所以回到了中断处理中,就到了中断返回的地址,再调用 iret 就到了目标进程的用户态程序去执行.

到这里 switch_to的功能就完成了

2.修改fork.c

和书中论述的原理一致,就是要把进程的用户栈、用户程序和其内核栈通过压在内核栈中的 SS:ESPCS:IP 关联在一起。

修改 fork() 的核心工作就是要形成如下图所示的子进程内核栈结构。

图片描述信息
fork 进程的父子进程结构
不难想象,对 fork() 的修改就是对子进程的内核栈的初始化,在 fork() 的核心实现 copy_process 中,p = (struct task_struct *) get_free_page();用来完成申请一页内存作为子进程的 PCB,而 p 指针加上页面大小就是子进程的内核栈位置,所以语句 krnstack = (long *) (PAGE_SIZE + (long) p); 就可以找到子进程的内核栈位置,接下来就是初始化 krnstack 中的内容了。

  • 修改copy_process()函数 /kernel/fork.c

    • 注释tss进程切换片段
    • 添加代码
      在这里插入图片描述
      copy_process()函数中添加
    	long *krnstack;krnstack = (long)(PAGE_SIZE +(long)p);*(--krnstack) = ss & 0xffff;*(--krnstack) = esp;*(--krnstack) = eflags;*(--krnstack) = cs & 0xffff;*(--krnstack) = eip;*(--krnstack) = ds & 0xffff;*(--krnstack) = es & 0xffff;*(--krnstack) = fs & 0xffff;*(--krnstack) = gs & 0xffff;*(--krnstack) = esi;*(--krnstack) = edi;*(--krnstack) = edx;*(--krnstack) = (long) first_return_kernel;*(--krnstack) = ebp;*(--krnstack) = ecx;*(--krnstack) = ebx;*(--krnstack) = 0;p->kernelstack = krnstack;

接下来就是对这段代码的解释

*(--krnstack) = ss & 0xffff;
*(--krnstack) = esp;
*(--krnstack) = eflags;
*(--krnstack) = cs & 0xffff;
*(--krnstack) = eip;

这五条语句就完成了上图所示的那个重要的关联,因为其中 ss,esp等内容都是 copy_proces() 函数的参数,这些参数来自调用copy_proces()的进程的内核栈中,就是父进程的内核栈中,所以上面给出的指令不就是将父进程内核栈中的前五个内容拷贝到子进程的内核栈中,图中所示的关联不也就是一个拷贝吗?
接下来的工作就需要和 switch_to 接在一起考虑了,故事从哪里开始呢?回顾一下前面给出来的 switch_to,应该从 “切换内核栈” 完事的那个地方开始,现在到子进程的内核栈开始工作了,接下来做的四次弹栈以及 ret 处理使用的都是子进程内核栈中的东西,

1: popl %eaxpopl %ebxpopl %ecxpopl %ebp
ret

为了能够顺利完成这些弹栈工作,子进程的内核栈中应该有这些内容,所以需要对 krnstack 进行初始化:

*(--krnstack) = ebp;
*(--krnstack) = ecx;
*(--krnstack) = ebx;
// 这里的 0 最有意思。
*(--krnstack) = 0;

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

想一想 first_return_from_kernel 要完成什么工作?PCB 切换完成、内核栈切换完成、LDT 切换完成,接下来应该那个“内核级线程切换五段论”中的最后一段切换了,即完成用户栈和用户代码的切换,依靠的核心指令就是 iret,当然在切换之前应该回复一下执行现场,主要就是 eax,ebx,ecx,edx,esi,edi,gs,fs,es,ds 等寄存器的恢复.

要将first_return_kernel(属于系统调用,而且是一段汇编代码)写在kernel/system_call.s头文件里面:

首先需要将first_return_kernel设置在全局可见:

.globl switch_to,first_return_kernel

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7OXbaPIg-1642342567292)(C:/Users/22064/AppData/Roaming/Typora/typora-user-images/image-20220116202135917.png)]

将具体的函数实现放在system_call.s头文件里面:

.align 2
first_return_kernel:popl %edxpopl %edipopl %esipop %gspop %fspop %espop %dsiret

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QMsLiR7g-1642342567292)(C:/Users/22064/AppData/Roaming/Typora/typora-user-images/image-20220116202241417.png)]

最后要记得是在 kernel/fork.c 文件里使用了 first_return_kernel 函数,所以要在该文件里添加外部函数声明

extern void first_return_kernel(void);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rOWY9yOs-1642342567293)(C:/Users/22064/AppData/Roaming/Typora/typora-user-images/image-20220116202406354.png)]
编译运行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P36SgUKw-1642342567295)(C:/Users/22064/AppData/Roaming/Typora/typora-user-images/image-20220116203058846.png)]
参考文章:
操作系统实验五 基于内核栈切换的进程切换(哈工大李治军)
哈工大-操作系统-HitOSlab-李治军-实验4-基于内核栈切换的进程切换

http://www.lbrq.cn/news/2472769.html

相关文章:

  • 哈尔滨教育学会网站建设网络服务平台
  • 遵义最新疫情通报西安网站关键词优化推荐
  • 怎么做盗版电影网站网络广告的特点
  • 网站开发公司网站官网百度一下你就知道官方网站
  • 招聘网站如何做薪酬报告东莞网络公司电话
  • 网站编辑信息怎么做百度应用商店下载安装
  • web网站开发里怎么切换界面搜索引擎技术
  • 网站建设价格方案滨州seo招聘
  • 代做网站收费标准国内最新新闻
  • 小学生信息科学做网站网站seo优化技巧
  • 网页设计与网站建设的区别竞价推广工作内容
  • ui设计就业方向有哪些?seo基础教程视频
  • 网站代码在哪看北京网站seo
  • 做石材外贸用什么网站营销平台
  • 阳东网站seo网络项目平台
  • iava是做网站还是app指数基金定投怎么买
  • 做教师知识网站有哪些内容厦门排名推广
  • 如何建一个论坛网站长沙百度网站推广优化
  • 自己搞网站建设百度热搜榜怎么打开
  • 婚纱摄影网站设计济南网站设计
  • 贵州网站建设gzzctyi一个新品牌怎样营销推广
  • 西安的商城网站建设平台连接
  • 阿里云 有企业 网站吗搜外友链
  • 黄浦区seo网站建设流量推广app
  • 做营销网站设计免费快速网站
  • 个人网站备案流程搜索百度下载安装
  • 做英语网站刷死粉网站推广
  • 丹阳做网站白百度一下你就知道
  • 做网站如何分页百度推广二级代理商
  • 如何查看网站权重百度关键词搜索排名
  • OpenCV学习探秘之一 :了解opencv技术及架构解析、数据结构与内存管理​等基础
  • 第10篇:实战验收篇
  • 小架构step系列26:Spring提供的validator
  • 备份一下我的 mac mini 的环境变量配置情况
  • 《Java 程序设计》第 6 章 - 字符串
  • 从一个“诡异“的C++程序理解状态机、防抖与系统交互