加入收藏 | 设为首页 | 会员中心 | 我要投稿 开发网_新乡站长网 (https://www.0373zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 搭建环境 > Unix > 正文

xv6源码阅读——进程切换

发布时间:2022-10-17 12:49:24 所属栏目:Unix 来源:
导读:  说明 概述

  从一个用户进程(旧进程)切换到另一个用户进程(新进程)所涉及的步骤:

  上面过程就简单阐述,是怎样进行进程切换的

  当然,里面会有很多细节,后面会详细介绍

  从用户
  说明 概述
 
  从一个用户进程(旧进程)切换到另一个用户进程(新进程)所涉及的步骤:
 
  上面过程就简单阐述,是怎样进行进程切换的
 
  当然,里面会有很多细节,后面会详细介绍
 
  从用户线程到内核线程
 
  这一部分在上一章是有过介绍的,通过中断机制,通过uservec保存寄存器,然后进入usertrap()
 
  usertrap
 
  void
  usertrap(void)
  {
    int which_dev = 0;
    if((r_sstatus() & SSTATUS_SPP) != 0)
      panic("usertrap: not from user mode");
     //省略 ··········
    // give up the CPU if this is a timer interrupt.
    if(which_dev == 2)
      yield();
    usertrapret();
  }
  其他部分就不过多赘述了(上一篇博客讲的很详细),通过判断中断类型,这是一个定时器中断,就调用yield()函数
 
  从旧进程进入调度进程 yield
 
  // Give up the CPU for one scheduling round.
  void
  yield(void)
  {
    struct proc *p = myproc();
    acquire(&p->lock);
    p->state = RUNNABLE;
    sched();
    release(&p->lock);
  }
  该函数干的事情很简单,先获取该进程的锁,将进程状态设置为RUNNABLE
 
  然后调用sched()函数进行后续操作
 
  题:为什么再设置进程状态前unix线程切换,要先获取该进程的锁呢
 
  因为一旦将进程状态设置为RUNNABLE,调度器就会认为该进程处于等待状态的可能会让他接下来运行,但实际上,该进程还是处于运行状态的
 
  如果这是一个多核CPU的,就可能会发生一个进程被两个CPU同时运行,程序可能会立马崩溃。
 
  所以我们要保证改变进程状态,保存寄存器,载入另一个进程的寄存器这三步是原子的。
 
  sched
 
  void
  sched(void)
  {
    int intena;
    struct proc *p = myproc();
    if(!holding(&p->lock))
      panic("sched p->lock");
    if(mycpu()->noff != 1)
      panic("sched locks");
    if(p->state == RUNNING)
      panic("sched running");
    if(intr_get())
      panic("sched interruptible");
    intena = mycpu()->intena;
    swtch(&p->context, &mycpu()->context);
    mycpu()->intena = intena;
  }
  swtch
 
  这是一段汇编代码,主要干的工作是将该进程的寄存器保存下来,然后载入调度进程的寄存器
 
 
  .globl swtch
  swtch:
   # 保存寄存器
          sd ra, 0(a0)
       # 省略```````````````````
          sd s11, 104(a0)
     # 恢复所要切换进程的寄存器
          ld ra, 0(a1)
       # 省略```````````````````
          ld s11, 104(a1)
        
          ret
  问题:为什么RISC-V中有32个寄存器,但是swtch函数中只保存并恢复了14个寄存器?
 
  因为swtch函数是从C代码调用的,所以我们知道Caller Saved Register会被C编译器保存在当前的栈上。
 
  该函数完成后,就进入了调度进程
 
  调度 scheduler
 
  void
  scheduler(void)
  {
    struct proc *p;
    struct cpu *c = mycpu();
    
    c->proc = 0;
    for(;;){
      //通过确保设备可以中断来避免死锁。
      intr_on();
      for(p = proc; p < &proc[NPROC]; p++) {
        acquire(&p->lock);
        if(p->state == RUNNABLE) {
          // 切换到所选的进(释放它的锁,然后在跳回我们之前重新获取它)
          p->state = RUNNING;
          c->proc = p;
          swtch(&c->context, &p->context);
          // 进程目前已完成运行。
          c->proc = 0;
        }
        release(&p->lock);
      }
    }
  }
  调用swtch后就会进入另一个进程了
 
  从调度进程进入新进程
 
  该过程也是在完成swtch后完成切换的
 
  他会从sched函数返回
 
  void
  sched(void)
  {
    int intena;
    struct proc *p = myproc();
    //、、、、、、、、、、、、、、
    swtch(&p->context, &mycpu()->context);
    mycpu()->intena = intena;
  }
  他会返回到swtch处,然后恢复 mycpu()->intena
 
  从内核线程返回用户线程
 
  这后面就是一步步返回,通过trap机制返回用户层,在上一篇博客讲的很详细,不过多赘述
 
  XV6线程第一次调用swtch函数 allocproc
 
  static struct proc*
  allocproc(void)
  {
    struct proc *p;
    //、、、、、、、、、、省略
    memset(&p->context, 0, sizeof(p->context));
    p->context.ra = (uint64)forkret;
    p->context.sp = p->kstack + PGSIZE;
    return p;
  }
  在创建进程时,会设置ra和sp
 
  ra很重要,因为这是进程的第一个switch调用会返回的位置。同时因为进程需要有自己的栈,所以ra和sp都被设置了。这里设置的forkret函数就是进程的第一次调用swtch函数会切换到的“另一个”线程位置。
 
  当调度线程将CPU交给该进程时,该进程会从forkret()函数处返回,执行该函数,
 
  // A fork child's very first scheduling by scheduler()
  // will swtch to forkret.
  void
  forkret(void)
  {
    static int first = 1;
    // Still holding p->lock from scheduler.
    release(&myproc()->lock);
    if (first) {
      // File system initialization must be run in the context of a
      // regular process (e.g., because it calls sleep), and thus cannot
      // be run from main().
      first = 0;
      fsinit(ROOTDEV);
    }
    usertrapret();
  }
  从代码中看,它的工作其实就是释放调度器之前获取的锁。函数最后的usertrapret函数其实也是一个假的函数,它会使得程序表现的看起来像是从trap中返回,但是对应的trapframe其实也是假的,这样才能跳到用户的第一个指令中。
 

(编辑:开发网_新乡站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章