xv6代码阅读:进程和进程调度

xv6的进程结构以及进程调度。

基本数据结构

涉及到进程的有三大数据结构:struct cpustruct contextstruct proc
struct cpu里面保存着当前CPU的相关信息,

proc.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Per-CPU state
struct cpu {
uchar id; // Local APIC ID; index into cpus[] below
struct context *scheduler; // scheduler context,即scheduler运行环境信息
struct taskstate ts; // 用于interrupt时寻找进程的内核栈
struct segdesc gdt[NSEGS]; // x86 global descriptor table
volatile uint started; // Has the CPU started?
int ncli; // Depth of pushcli nesting.
int intena; // Were interrupts enabled before pushcli?

// Cpu-local storage variables
struct cpu *cpu;
struct proc *proc; // The currently-running process.
};

struct context里面保存着运行环境信息,如果进程context,scheduler context,

1
2
3
4
5
6
7
struct context {
uint edi;
uint esi;
uint ebx;
uint ebp;
uint eip; // eip不会被显示设置,它在对swtch()函数的call和ret时被设置
};

struct proc里面保存着一个进程的信息,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Per-process state
struct proc {
uint sz; // Size of process memory (bytes)
pde_t* pgdir; // Page table,页表,代表用户进程地址空间
char *kstack; // Bottom of kernel stack for this process,代表进程的内核栈
enum procstate state; // Process state
volatile int pid; // Process ID
struct proc *parent; // Parent process
struct trapframe *tf; // Trap frame for current syscall
struct context *context; // swtch() here to run process
void *chan; // If non-zero, sleeping on chan
int killed; // If non-zero, have been killed
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
};

进程调度

进程调度分为两个部分:进程调出和进程调入。

  • 调入
    CPU会一直循环在scheduler()函数中,
    符从ptable中寻找RUNNABLE进程,然后将其调入,即运行。
  • 调出
    调出也就是从用户态进程进入内核,如进程sleep, exit或者中断,调出在sched()中进行。

调入部分已经说过很多了,体现在scheduler()函数中,那么调出呢?

proc.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void sched(void)
{

int intena;

if(!holding(&ptable.lock))
panic("sched ptable.lock");
if(cpu->ncli != 1)
panic("sched locks");
if(proc->state == RUNNING)
panic("sched running");
if(readeflags()&FL_IF)
panic("sched interruptible");
intena = cpu->intena;
swtch(&proc->context, cpu->scheduler);
cpu->intena = intena;
}

对比scheduler()函数,调入、调出其实就是在swtch()而已:

1
2
3
4
// 调入
swtch(&cpu->scheduler, proc->context);
// 调出
swtch(&cpu->scheduler, proc->context);

调入就是:cpu->scheduler => proc->context
调出就是:proc->context => cpu->scheduler

swtch()的作用就是保存旧的context,然后切换到新的context并弹出寄存器值。

cpu指当前CPU,proc指当前进程。在调出和调入之间,需要kernel运行(不然谁来调啊),
其实这就相当于有三者:old process context, kernel context, new process context。

当进程sleep、exit或者发生时钟中断时,当前进程就会被切换出去,当scheduler()找到它时,
再把它切换进来。

(over)