怎么实现进程切换

是什么

进程是一个运行中的程序实体,拥有独立的地址空间和逻辑控制流。

void sayHi()
{
  printf("%s\n", "Hello,World");
  return 0;
}

sayHi就是一个函数,它一旦运行起来,就是进程。

独立的逻辑控制流,是说这个进程就像独占一个CPU一样。每个进程使用CPU的时间不是连续的,但它们的指令运行却是前后衔接的,不会受到其他进程的指令对它的指令和数据大的更改。

运行起来

进程,这里是指用户进程(区别于内核),处在特权级3。内核在特权级0。CPU从通电运行起,就处在特权级0。要运行用户进程,需要从特权级0转移到特权级3。

CPU执行哪条指令,受cs:eip控制。有一个指令,既能够实现特权级从高向低转移,又能更新cs:eip。这个指令就是iretd

执行第一个用户进程的方法是,把cs、eip、ss、esp等寄存器需要的值入栈,然后用irted出栈,用户进程就能运行起来了。示意代码如下。注意,下面的代码只是说明大概思路,不是可执行的代码。

push		ss
push		esp
push		cs
push		eip

irted

停下来

操作系统要让一个CPU能运行多个进程。一个进程不能总是独占CPU,必须让它停下来,把CPU让给其他进程使用。

时钟中断每隔一段时间就会停止当前进程,转移到执行中断例程。

切换

在中断例程中,我们可以为当前进程A建立一个”快照“,选择运行进程B。

进程的”快照“,是把进程正在使用的数据、下一条要运行的指令等信息存储起来;然后选择另外一个进程,从存储设备中取出这个进程运行所需要的所有信息,最后运行这个进程。

进程正在使用的数据,全部都在寄存器中,把寄存器中的数据存起来,就是为进程建立了快照。

把数据存储到哪里呢?在汇编语言中,动态存储数据,我发现只有堆栈可以使用。进程的切换就是这样一个流程:

  1. 进程A正在运行,时钟中断发生,执行中断例程。
  2. 从TSS的esp0中获取进程A的堆栈栈顶,把esp指向这个栈顶。
  3. 把寄存器中的值都压入进程A的堆栈中。
  4. esp指向进程B的堆栈。调度程序就在这个步骤。
  5. 把TSS的esp0指向进程B的堆栈的最高地址处加4个字节。下一次中断发生时,TSS获取的esp0的值就是在这里获取的。
  6. B的堆栈出栈。
  7. 使用iretd出栈ss、esp、cs、eip,开始执行进程B的指令。

堆栈转移

从上面的切换过程,很容易看出,需要转移堆栈。

第1步~~~第3步,堆栈从用户进程中的不知名堆栈转移到存储进程A数据的堆栈。这个堆栈,为进程建立快照使用。每个进程都有一个这样的堆栈。

在第4步前后,都有一次堆栈切换:

  1. 前面,从A进程的堆栈切换到内核堆栈。进程调度程序很有可能会使用堆栈。如果仍然使用进程A的堆栈,会破坏A的堆栈,重新运行A时会有许多麻烦。
  2. 进程调度结束后,已经选择了进程B,需要从堆栈中恢复B的数据,于是把esp指向B的堆栈。

进入内核后,我们切换过GDT和内核堆栈。当初我觉得没必要切换内核堆栈,现在终于发现了内核堆栈的作用。

那个内核堆栈是这样建立的:先用0填充一段内存空间,比如4KB,然后在下面设置一个标号,这个标号就是内核堆栈。代码如下:

StatckStrace		1024  resp		0
TopStack:

上一篇:多厂商***系列之一:加密基础与IPSec【附带思科与H3C的配置介绍】


下一篇:汇编函数阅读笔记