zoukankan      html  css  js  c++  java
  • xv6进程切换-swtch函数

    https://blog.csdn.net/Swartz2015/article/details/61615603

    xv6进程切换-swtch函数

    进程切换中由于需要保存当前进程的寄存器状态信息,又要将新进程记录的寄存器状态信息加载到寄存器,因此涉及到许多栈的操作,堆栈间的来回切换,容易让人眼花缭乱,难以理解。本文试图分析以下xv6中的进程切换过程。

    当前进程通过调用yield函数,进行进程切换。yield函数调用sched函数,sched函数启动swtch函数完成进程切换。整个流程是这样的:

    yield->sched->swtch
    • 1

    在sched函数中,可以看到,当前进程总是先切换到当前cpu的scheduler切换器:

    //void sched(void)
    swtch(&proc->context, cpu->scheduler);
    • 1
    • 2

    切换器是一个死循环,该循环不断在进程表中扫描,选择一个RUNNABLE的进程调度,即从scheduler切换器转换到新选择的进程:

    //scheduler切换器
    void
    scheduler(void)
    {
      struct proc *p;
    
      for(;;){
        // Enable interrupts on this processor.
        sti();
    
        // Loop over process table looking for process to run.
        acquire(&ptable.lock);
        for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
          if(p->state != RUNNABLE)
            continue;
    
          // Switch to chosen process.  It is the process's job
          // to release ptable.lock and then reacquire it
          // before jumping back to us.
          proc = p;
          switchuvm(p);
          p->state = RUNNING;
          swtch(&cpu->scheduler, proc->context);
          switchkvm();
    
          // Process is done running for now.
          // It should have changed its p->state before coming back.
          proc = 0;
        }
        release(&ptable.lock);
    
      }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    显然,swtch函数是重点。swtch函数的原型是:

    void swtch(struct context **old, struct context *new);
    • 1

    它的工作主要包括:1. 保存当前(old)进程的上下文。 2. 加载新进程(new)的上下文到机器寄存器中。

    contex中其实就是几个寄存器变量,用来保存这些寄存器的值。

    swtch的函数代码如下:

    # Context switch
    #
    #   void swtch(struct context **old, struct context *new); 
    # 
    # Save current register context in old
    # and then load register context from new.
    
    .globl swtch
    swtch:
      movl 4(%esp), %eax
      movl 8(%esp), %edx
    
      # Save old callee-save registers
      pushl %ebp
      pushl %ebx
      pushl %esi
      pushl %edi
    
      # Switch stacks
      movl %esp, (%eax)
      movl %edx, %esp
    
      # Load new callee-save registers
      popl %edi
      popl %esi
      popl %ebx
      popl %ebp
      ret
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    下面分析以下swtch函数的执行过程。


    swtch函数的执行过程

    当任意进程调用swtch函数时,该进程的堆栈是下面这个样子的:


    这里写图片描述

    其中栈中每个存储区域都是4个字节。这里需要提一下,在执行指令call时,当前指令的下一条指令的地址将会push到栈中,也就是上面看到的eip,这样就可以保证在函数返回时,可以回到函数调用的下一条指令的地方。然后swtch开始执行,

    movl 4(%esp), %eax
    movl 8(%esp), %edx
    • 1
    • 2

    所以寄存器eax和edx都会指向如下图所示的位置:


    这里写图片描述

    接下来会依次将有关上下文信息的寄存器push到栈中
      pushl %ebp
      pushl %ebx
      pushl %esi
      pushl %edi
    • 1
    • 2
    • 3
    • 4

    push完之后,就可以堆栈布局就变成下图所示:


    这里写图片描述

    对比contex和当前的栈布局,可以直观看到,此时在栈上存储的刚好是一个contex。当前的栈顶esp刚好指向当前进程的contex中的第一个元素的地址,所以这里可以说contex中包括了栈信息和寄存器信息,不仅可以用它来进行栈切换,还可以通过它恢复寄存器。因此可以想到,下次重新调度这个进程时,只需要直接将栈上的信息弹到相应寄存器中就可以了。而这个栈顶指针,我们可以直接用proc->contex指针记录就可以了。即

    proc->contex = esp;
    • 1

    我们继续看swtch函数:

    # Switch stacks
      movl %esp, (%eax)
      movl %edx, %esp
    • 1
    • 2
    • 3

    根据注释也可以看到,这两行主要是用于栈的切换。前面我们知道,eax中存储的是指向old_proc->contex的指针,old_proc->contex是指向当前进程的contex的指针,所以movl %esp, (%eax),相当于:

    old_proc->contex = esp;
    • 1

    也就是让当前进程的contex指针指向栈顶。根据前面的示意图我们可以理解原理。

    到此为止,旧进程的contex保存工作已经完成了。

    下面的工作自然是怎么把新进程的contex弹出到对应的寄存器中。其实保存过程和弹出恢复过程是对称的

    从保存过程中,我们知道新进程的contex信息总是可以通过new_proc->contex获得,因为new_proc->contex指向了new_proc内核栈的栈顶,而栈顶依次保存着上下文寄存器信息所以将new_proc->contex赋值到esp就完成了堆栈切换,再依次pop就可以恢复上下文寄存器信息。但是我们怎么获得new_proc的contex呢?回到前面,我们看到,就进程在调用swtch时,就把new_proc的contex放在了堆栈中,并且根据前面edx=new_proc->contex。所以堆栈切换就是把

    esp= edx;
    • 1

    然后

    popl %edi
    popl %esi
    popl %ebx
    popl %ebp
    • 1
    • 2
    • 3
    • 4

    就可以依次将new_contex中保存的寄存器值依次弹回到对应寄存器中了。

    最后的

    ret
    • 1

    指令将eip弹回。所以新进程会从它上次调用swtch函数的下一条指令开始执行。

  • 相关阅读:
    springmvc两种非注解的处理器适配器
    springmvc两种非注解的处理器映射器
    java线程数据交换Exchanger
    java计数器CountDownLatch
    java路障CyclicBarrier
    java线程condition
    java模拟数据库缓存
    java中的递归
    面向对象
    全概率公式和贝叶斯准则
  • 原文地址:https://www.cnblogs.com/itzxy/p/9293677.html
Copyright © 2011-2022 走看看