Search code examples
coperating-systemriscvxv6

I can't understand this line of code in xv6


Despite consulting the documentation, I still can't understand this line:swtch(&c->scheduler, &p->context);.

My question: I know this line is to switch p->context, including save registers and restore registers, but I can't understand the pc change in this process, that is what execution order of this code? After theswtch(&c->scheduler, &p->context); is executed, is it immedidate to execute c->proc = 0;, then the effect of executing c->proc=p; disappears? I am confused now.

code link: https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/proc.c line 456


*// Per-CPU process scheduler.*
*// Each CPU calls scheduler() after setting itself up.*
*// Scheduler never returns.  It loops, doing:*
*//  - choose a process to run.*
*//  - swtch to start running that process.*
*//  - eventually that process transfers control*
*//    via swtch back to the scheduler.*
void
scheduler(void)
{
  struct proc *p;
  struct cpu *c = mycpu();

  c->proc = 0;
  for(;;){
    *// Avoid deadlock by ensuring that devices can interrupt.*
    intr_on();
    int found = 0;
    for(p = proc; p < &proc[NPROC]; p++) {
      acquire(&p->lock);
      if(p->state == RUNNABLE) {
        *// Switch to chosen process.  It is the process's job*
        *// to release its lock and then reacquire it*
        *// before jumping back to us.*
        p->state = RUNNING;
        c->proc = p;
        swtch(&c->scheduler, &p->context);

        *// Process is done running for now.*
        *// It should have changed its p->state before coming back.*
        c->proc = 0;
        found = 1;
      }
      release(&p->lock);
    }
    if(found == 0){
      intr_on();
      asm volatile("wfi");
    }
  }
}

Solution

  • This is literally the code that was so confusing that its original authors wrote "You are not expected to understand this" in the comments, so don't feel bad for not understanding it.

    The key thing you may have missed is that p->context contains an address where swtch is to resume execution of process p. It's set up, for instance, here:

      // Set up new context to start executing at forkret,
      // which returns to user space.
      memset(&p->context, 0, sizeof(p->context));
      p->context.ra = (uint64)forkret;
      p->context.sp = p->kstack + PGSIZE;
    

    So, when the scheduler calls swtch, it is effectively making an indirect function call to whatever p->context.ra points to. That code will execute for some indefinite period, and then eventually (sort of) return to swtch, which returns to the scheduler, which continues with c->proc = 0.

    (In the above sentence, the words "sort of" and "effectively" are doing a lot of work. To understand what's hiding behind those words, the next thing you should read up on is coroutines.)