操作系统的那棵“树”---06

Posted 大忽悠爱忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了操作系统的那棵“树”---06相关的知识,希望对你有一定的参考价值。

操作系统的那棵“树”---06


操作系统的那棵“树”

今天从一颗开始,我们看看如何从小树苗长成一颗苍天大树。


运转CPU


CPU运转起来很简单,就是不断的从内存取值执行。


CPU没有好好运转


IO是个耗费时间的活,如果CPU在取值执行过程中,遇到了IO指令,那么必须等当前IO执行完毕后,才能继续取出下一条指令去执行,显然这种同步等待机制,并没有充分利用CPU的性能。


得让CPU好好运转


如何解决上面同步等待的问题呢?

  • 程序间交替切换执行,程序1执行到IO指令阻塞时,切换到程序2执行

从A跳到B我们并不陌生

程序间交替执行,意味着程序间需要来回跳转执行,既然需要跳转,就需要保护现场和恢复现场,那么对应的就需要用栈来完成这两个任务。


一个栈+Yield造成的混乱

既然需要用栈来保存现场和恢复现场,那么处于节约内存考虑,一个栈够吗?

显然不行,大家自己推一遍过程就知道了,那既然不行,该怎么办呢?


两个栈+两个用户TCB

既然一个栈,那就两个栈,既然有了两个栈,随之就引出了一个问题,在两个栈切换时,如何知道当前栈的栈顶位置呢?

例如: esp栈顶指针寄存器一开始指向线程1的栈顶,但是此时要切换到线程2,那么就需要把esp指针移动到线程2的栈顶位置,那么线程2的栈顶位置搁哪保存呢?

为了方便切换时,找到对应线程的栈顶位置,因此由了TCB的诞生,TCB中保存当前线程栈顶位置和其他一些信息,因此如果要进行线程切换,首先需要通过下一个TCB,再通过TCB找到新的栈顶位置,然后将esp指针指向新栈顶位置。


一直在用户态那怎么行?


因为用户级线程的切换都是在用户态完成的,内核态是不知道当前进程中有多少个用户级线程的,那么一但进程1中某个用户级线程进入内核态后,产生了阻塞,那么此时是无法切换到进程1中其他用户级线程继续执行的,而是会直接切换到进程2继续执行。


引入内核栈的切换

既然用户级线程在用户态完成的线程切换,内核态看不见,不知道。

那么对应就有了在内核态完成切换的线程,即内核级线程。

因为通过中断进入内核态,再通过中断返回时,也是需要回到进入中断前用户态的状态的,那么就需要在内核态中设置一个栈,来保存中断进入时,用户态的状态,该栈就被叫做内核栈。

当然,因为内核态中会去进行系统调用,也需要调用函数,那么也会有保护现场和恢复现场的需要,因此肯定也是需要一个栈的。

有人问,为什么不直接使用用户栈来保存相关记录呢? 而非要在内核态再创建一个内核栈,不是浪费内存吗?

  • 因为用户态和内核态本来就是两个独立的区域,并且用户态是无法直接访问内核态的,现在将内核态的相关记录放在用户态中保存,这合适吗?


随之就引入了内核栈,每个内核级线程对应一个用户栈,一个内核栈,并且因为切换是通过内核栈完成的,因此TCB中保存的是内核栈的栈顶位置。

  • 线程1通过中断进入内核态,中断过程中会将用户态的相关状态压入内核栈,然后因为IO陷入阻塞状态,此时引起了线程切换
  • 因为ESP此时指向线程1的栈顶位置,因此首先将ESP指向的内核栈顶位置放入线程1关联的TCB1中保存。
  • 通过调度算法,找到下一个切换的线程2,然后首先得到该线程关联的TCB2
  • 从TCB2中取出线程2内核栈栈顶位置,然后放入ESP中,此时ESP切换了指向,指向了线程2栈顶位置
  • 线程2开始执行,然后最终会执行一条iret指令,弹出先前保存在内核栈中的用户态状态,线程2返回到了用户态继续执行

到实现idea的时候了

如果在屏幕上交替打印出A和B呢?


从用户代码开始

如果要写出交替打印A和B的程序,不就是创建两个进程,一个不断打印A,另一个不断打印B吗?


程序是什么?就是人的思维的C表达

如果把上面c语言,翻译成汇编形式,就是下面这样:

首先一上来先通过fork来创建一个进程,fork函数通过int 0x80号中断进入内核,下面看看他干了啥?


INT进入内核


int 0x80要进入内核态,中断过程中会将用户栈状态和当前标志寄存器,EIP,CS等都压入内核栈保存。

int 0x80会去进行系统调用,首先通过中断类型号0x80加上系统调用号,最终定位到sys_fork函数。


开始sys_fork


sys_fork最终会跳转到copy_process处执行。


开始copy_process

copy_process主要做的工作就是初始化PCB和当前进程对应的TSS,而新创建进程的用户态状态基本都copy父进程

包括一会该子进程开始执行的时候,也是直接从父进程进入中断时,压入栈中的EIP处开始执行,并且将eax设置为了0,这样就可以确保子进程去执行自己的代码,而不会与父进程执行相同的指令序列。


开始返回…


父进程执行完sys_fork后返回,返回后需要判断是否进入阻塞,时间片是否到期,然后这里假设这里父进程不满足切换条件,然后返回到用户态,继续去创建进程B。


main继续执行,现在我们有了什么?


进程B的创建和进程A一样,只不过此时进程A和进程B形成了一个进程就绪队列


main继续,到了哪里?

父进程创建完进程A和进程B后,进入等待状态。

wait函数,也会进行系统调用,底层会将自己的状态设置为阻塞态,然后进行进程调度。


schedule

假设此时调度算法,默认选中就绪队列中第一个元素,即切换到进程A执行。


switch_to切换


switch_to简而言之就是先将当前CPU状态拍到父进程的TSS中,然后再将进程A中的TSS状态信息拍到CPU上。


接下来会怎么样?

因为进程A的TSS中设置的初始EIP=100,并且eax等于0,因此当开始执行进程A时,首先判断eax是否为0,如果为0,则满足条件,跳转到208处执行,即不断打印A。


我们的目标是什么?

上面,我们完成了进程A的执行,进程A会不断在屏幕上打印A,那么我们的期望是A和B不断交替打印,那就需要让B进程也执行起来,然后A进程和B进程交替执行


时钟中断

加入时钟中断,每产生一次时钟中断,就把当前进程的counter–,当某次时钟中断发生时,当前进程–counter=0,说明当前进程的时间片用完了,需要进行切换。


有那么一次时钟中断

当进程A的时间片用完后,需要切换到进程B继续执行。


schedule+switch_to

通过switch_to将当前CPU状态扣到进程A的TSS上面,然后将进程B的TSS拍到CPU上面,就完成了进程的切换。


接下来会怎么样?

接下来,进程B开始执行,然后不断去打印B


我们的目标达到了吗?

交替的打出A和B…

已经打出了B,完事了吗? 何为交替? 接下来会发生什么?把自己变成计算机想一想…

中断,仍然是中断…什么中断?


又有那么一次时钟中断, 再一次schedule+switch_to

然后,当进程B打印了一会B后,有因为进程B的时间片到期,切换到进程A继续执行。


接下来会怎么样?


而接下来,就会重复因为时间片到期,进程间不断切换,从而完成A和B交替打印的结果

以上是关于操作系统的那棵“树”---06的主要内容,如果未能解决你的问题,请参考以下文章

最小生成树

决策树交叉验证问题

克鲁斯卡尔

BZOJ1502:[NOI2005]月下柠檬树——题解

[BZOJ1502]月下柠檬树(自适应辛普森积分)

图的最小生成树(普利姆prim算法)