【linux0.12】实验楼操作系统原理与实践之实验五:基于内核栈切换的进程切换

13 篇文章 0 订阅
10 篇文章 0 订阅

简介

      早期内核使用intel提供的基于tss切换的机制,这种机制操作十分简单,但是执行速度特别慢,无法充分利用现代cpu的一些指令流水来优化速度。关于tss切换的方式详细可以看实验楼中的描述,和完全剖析这本书。根据老师的实验,要把这种基于tss切换的方式换成现代的基于内核栈切换的方式。
      基于堆栈的切换,核心就是要先在切换前保存进程的栈,然后切换到另一个进程的栈,但因为在内核中,又有了其他的工作要做。

前提知识

      因为要在内核中切换栈,所以要对ss esp ebp寄存器的作用很熟悉。ss用来寻找内核栈段,esp就是栈顶,而ebp寄存器则用来设置c函数中栈帧的位置。关于这点,可以看我的这篇文章,简单介绍了下其中esp ebp的作用。更详细可以看linux内核完全剖析这本书中关于c语言和汇编的互相调用,图文并茂,十分详细与精彩。
      还有关于pcb(process control block)的知识也要了解一些,pcb是内核中描述进程的数据结构,里面就是进程的各种资源的记录,在这里即是task_struct数据结构。
      还有最重要的一点是,当通过中断,完成从用户态到内核态的切换时,中断指令会自动把用户态的一些状态(ss esp eflags cs eip)压到内核栈,方便以后恢复到用户态。也就是在这里,将内核栈和用户栈关联了起来,完成了一套栈的设置,也就完成了基于内核栈进程切换的基础。

过程

      以时钟中断,进程时间片为0进而引起进程切换为例。首先在0号进程的初始化过程中设置0x20的中断处理为时钟中断,并初始化中断函数入口(time_interrupt函数)。如果时钟中断发生,则通过0x20进入内核的时钟中断函数内,同时0x20指令把用户态的ss esp eflags cs ip寄存器压入内核栈。进入time_interrupt后,会进行一系列的压栈,也是为了保存用户进程状态,然后进行一些初始化后调用do_timer函数,也就是真正的时钟中断处理,在do_timer中判断时间片为0后,进入schedual函数重新调度,在schedual函数中找到下一个时间片最大的进程(调度策略),然后进入switch_to切换进程。在switch_to中处理schedual的栈帧,然后切换pcb,切换内核栈的位置,切换ldt以隔绝不同进程的地址空间,然后ret返回。ret返回到schedual的"}“语句,继续返回,到do_timer的”}"的语句,继续返回到timer_interrupt,然后跳到ret_from_sys_call中,核心主要就是一系列的pop和一个iret语句,iret语句就将0x20压入的ss esp eflags cs eip弹出,进而返回到用户栈继续执行未完成的语句。至此,进程切换结束。
      另一个例子是fork的执行,毕竟刚开始时只有进程0,只有fork出别的进程,才会有上述的切换过程。fork是一个系统调用,通过0x80中断进入。0x80将ss esp eflags,cs eip寄存器压入内核栈中,跳到系统中断处理函数system_call中,进行一系列的压栈保存用户状态后在sys_call_table中找到fork函数的入口,进入sys_fork,sys_fork仍然是一个汇编函数,首先找到一个未使用的进程号,然后进入copy_process函数,从名字可以看出,它的作用是拷贝进程。
      copy_process这个函数有很多参数,都是之前的汇编函数压入的。功能主要就是找到一页空闲内存做新进程的pcb和内核栈,将current进程的Pcb赋值给新进程的pcb,对新进程的一些属性做初始化,我们的改动点主要在内核栈的初始化。因为这是一个新的进程,现在还没有自己的用户进程,是和current进程共用一个用户进程,所以它的用户状态和current进程的用户状态时一样的。因为是新进程,所以需要手动设置ss esp eflags cs eip的值,方便以后在以后的时钟中断调度和switch_to中弹出。可能有读者会疑惑,这五个寄存器不是在时钟中断中设置的嘛?请注意,中断指令设置的是current进程的寄存器状态,而新进程的寄存器状态是由iret直接返回的,所以新进程的这5个寄存器状态需要手动设置。当下一次切换到这个新进程时,switch_to最后执行的语句是几个pop和ret指令,同样的,这几个pop的操作数需要我们在fork中压入栈中,ret需要弹出一个32位的地址作为eip来跳转到该地址继续执行,同样需要我们在fork中压入一个函数地址。这个函数可以叫做first_return_from_kernel,功能主要是一些pop恢复用户状态(和return_from_sys_call的作用一样)和iret指令恢复用户态的ss esp eflags cs ip,由此fork的新进程就恢复到了和父进程同样的位置。

代码

https://github.com/sjc2870/oslab/commit/69d6a7b0a6a121b198f9ac59d56f608f51ec977b
可以看这次的提交变动记录

总结

      copy_process改动前后的对比:改动前,子进程的切换是通过执行switch_to中的tss切换直接恢复用户态的所有寄存器,然后恢复到用户态执行,在switch_to内部就已经完成了切换,而switch_to的剩余代码没有执行。而改动后,是通过switch_to中的ret指令切换到first_reurn_from_kernel,再通过其中的pop和iret返回到用户态执行。
时钟中断导致进程切换的流程图:
在这里插入图片描述
fork新进程的流程图:
在这里插入图片描述
这里的fork只写了下自己增加的功能,如果要详述的话,估计又要一篇文章了,而且这里应该结合新进程的switch_to来看,这样更容易看清楚为什么要这样做。

参考

哈工大李治军老师操作系统讲解视频
实验楼:操作系统原理与实践实验五
linux内核完全剖析0.12--------赵炯

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值