6.S081 附加Lab2 CPU进程调度(Scheduler)

6.S081 附加Lab2 CPU进程调度(Scheduler)

本实验主要是衍生于 (6.S081-7中断 - Interrupts)的看OS启动源码的过程。
——但是本篇的内容十分浅显(准确来说是错误),我后来发现,scheduler的执行过程,并不是从上往下执行,而是一种奇怪的转交CPU的方式,这一切都因为swtch有趣的实现,它首先保存了进程的上下文,并load了cpu的上下文,并且让当前进程将cpu控制权交给其他线程。具体内容可以看我的博客6.S081 附加Lab3 线程切换——源代码实现(trap,yeild,context,Scheduler)

博客6.S081 附加Lab3 线程切换——源代码实现(trap,yeild,context,Scheduler)的精彩摘要👇

  • 线程调度的步骤(—— 能看懂这段话,后面的实验就不用看了,本篇文章就是在讲这个过程。)首先计时器通过触发中断,将CPU控制权交给内核(内核的trap程序,然后trap的处理是调用yield函数),yield函数调用的swtch在保存了即将换出的进程的context(包括栈指针)之后,将CPU控制权,交给了内核的调度程序scheduler,内核调度程序scheduler通过它调用的swtch,将CPU的控制权交给下一个即将调度进来的程序。

本实验的主要内容是,分析xv6操作系统的进程调度(其实是错误理解,正确过程比这个复杂得多,请看6.S081 附加Lab3 线程切换——源代码实现(trap,yeild,context,Scheduler))。

1. OS调用scheduler

下面是OS的main函数,我们可以看到,最后一行,运行了scheduler函数。

volatile static int started = 0;

// start() jumps here in supervisor mode on all CPUs.
void
main()
{
  if(cpuid() == 0){
    consoleinit();
    printfinit();
    printf("\n");
    printf("xv6 kernel is booting\n");
    printf("\n");
    kinit();         // physical page allocator
    kvminit();       // create kernel page table
    kvminithart();   // turn on paging
    procinit();      // process table
    trapinit();      // trap vectors
    trapinithart();  // install kernel trap vector
    plicinit();      // set up interrupt controller
    plicinithart();  // ask PLIC for device interrupts
    binit();         // buffer cache
    iinit();         // inode cache
    fileinit();      // file table
    virtio_disk_init(minor(ROOTDEV)); // emulated hard disk
    userinit();      // first user process
    __sync_synchronize();
    started = 1;
  } else {
    while(started == 0)
      ;
    __sync_synchronize();
    printf("hart %d starting\n", cpuid());
    kvminithart();    // turn on paging
    trapinithart();   // install kernel trap vector
    plicinithart();   // ask PLIC for device interrupts
  }

  scheduler();        
}

2. Scheduler的作用

下面是void scheduler(void);👇,注释部分解释了进程调度的作用:

永远处于循环中(永远不退出)。

  • 选择一个进程(接下来去运行它);—— 从代码来看就是从进程队列里面选择一个进程。

  • 通过swtch函数让这个进程运行

    c->proc = p;						// cpu当前执行的process = 被选中即将需要执行的process
    swtch(&c->scheduler, &p->context);	// context是OS的上下文,即PC,sp还有通用寄存器
    

​ —— 切换c-> proc = p就顺带切换了页表等内容(同时c -> proc -> context自然等于p->context),那么为什么还要写swtch(&c->scheduler, &p->context);?。——因为这里的swtch(&c->scheduler, &p->context);就是让cpu的寄存器赋值成p被保存的寄存器(包括pc, sp等,因此一旦被赋值了,将会自动运行该进程的命令。)

  • 最终该进程将控制权(通过swtch函数)交还给scheduler
// 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");
    }
  }
}

其中struct cpu, struct procstruct context分别如下所示👇。

cpu结构体:

// Per-CPU state.
struct cpu {
  struct proc *proc;          // The process running on this cpu, or null.
  struct context scheduler;   // swtch() here to enter scheduler().
  int noff;                   // Depth of push_off() nesting.
  int intena;                 // Were interrupts enabled before push_off()?
};
extern struct cpu cpus[NCPU];

context:内核上下文结构体

最关键的部分,切换进程,其实就是swtch(&c->scheduler, &p->context);—— 这是因为,这里面包含了pc,sp,还有通用寄存器,只要有这些内容,CPU就可以自动执行指令了

// Saved registers for kernel context switches.
struct context {
  uint64 ra; // pc
  uint64 sp;

  // callee-saved
  uint64 s0;
  uint64 s1;
  uint64 s2;
  uint64 s3;
  uint64 s4;
  uint64 s5;
  uint64 s6;
  uint64 s7;
  uint64 s8;
  uint64 s9;
  uint64 s10;
  uint64 s11;
};

进程结构体:

这里的注释很详细,不再多讲。

enum procstate { UNUSED, SLEEPING, RUNNABLE, RUNNING, ZOMBIE };

// Per-process state
struct proc {
  struct spinlock lock;

  // p->lock must be held when using these:
  enum procstate state;        // Process state
  struct proc *parent;         // Parent process
  void *chan;                  // If non-zero, sleeping on chan
  int killed;                  // If non-zero, have been killed
  int xstate;                  // Exit status to be returned to parent's wait
  int pid;                     // Process ID

  // these are private to the process, so p->lock need not be held.
  uint64 kstack;               // Bottom of kernel stack for this process
  uint64 sz;                   // Size of process memory (bytes)
  pagetable_t pagetable;       // Page table
  struct trapframe *tf;        // data page for trampoline.S
  struct context context;      // swtch() here to run process
  struct file *ofile[NOFILE];  // Open files
  struct inode *cwd;           // Current directory
  char name[16];               // Process name (debugging)

  // added by levi
  pagetable_t levi_kernel_pagetable;
};

3. 总结

进程调度Scheduler由OS的main调用,调用后它永远处于循环中(永远不退出)。它的过程如👇

  • 选择一个进程(接下来去运行它);—— 从进程队列里面选择一个进程。

  • 通过swtch函数让这个进程运行

    c->proc = p;						// cpu当前执行的process = 被选中即将需要执行的process
    swtch(&c->scheduler, &p->context);	// context是OS的上下文,即PC,sp还有通用寄存器
    

​ —— 切换c-> proc = p就顺带切换了页表等内容;

​ ——swtch(&c->scheduler, &p->context);就是让cpu的寄存器赋值成p被保存的寄存器(包括pc, sp等,因此一旦被赋值了,将会自动运行该进程的命令。)

  • 最终该进程将控制权(通过swtch函数)交还给scheduler
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值