一、调度的必要性
任何操作系统运行的进程数量都可能超过计算机的CPU数量,因此必须制定一个方案,使各进程能够分时共享CPU。这就涉及到进程的切换,这种切换就是调度。调度的目的是实现多路复用。
二、实现多路复用的注意点
多路复用是一个具有挑战性的机制。xv6采用下面技术来实现多路复用:
- 上下文切换可以实现多路复用,但xv6中的上下文切换是最不透明的代码之一
- xv6使用定时器中断来驱动上下文切换
- 许多CPU会在进程间并发切换,锁可以用来避免竞争
- 当进程退出时,必须释放进程的内存和其他资源,但进程本身不能完全释放掉所有资源,比如它不能在使用内核栈的同时释放内核栈
- 多处理器计算机的每个CPU必须记住它正在执行的进程
三、何时调度
xv6仅在下面两种情况下会发生调度:
- 当一个进程等待设备或管道I/O,或在sleep系统调用中等待
- 定时器中断引起的调度
四、与进程切换有关的数据结构
xv6运行在多核处理器上,并且一个核可并发运行多个进程,为了实现进程调度等功能,我们需要数据结构来标识不同的核与不同的进程。
(一)cpu
struct cpu {
struct proc *proc; // The process running on this cpu, or null.
struct context context; // swtch() here to enter scheduler().
int noff; // Depth of push_off() nesting.
int intena; // Were interrupts enabled before push_off()?
};
xv6为每个CPU都维护了一个cpu结构体,它详细记录了该CPU的对xv6有用的信息。每个CPU都有一个编号——hartid,该编号保存在每个CPU的mhartid寄存器中。xv6可以通过该编号来索引分辨不同的CPU。
这里值得注意的是,如果想获得当前CPU的信息,但是这时发生了定时器中断,可能当前的执行的CPU会变成其他的CPU,那么获得的CPU信息就有误,为了保证正确性,会在读CPU信息前关闭定时器中断,读完后才会打开。
(二)process
struct proc {
struct spinlock lock;
// p->lock must be held when using these:
enum procstate state; // Process state
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
// wait_lock must be held when using this:
struct proc *parent; // Parent process
// these are private to the process, so p->lock need not be held.
uint64 kstack; // Virtual address of kernel stack
uint64 sz; // Size of process memory (bytes)
pagetable_t pagetable; // User page table
struct trapframe *trapframe; // 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)
};
它的设计理念与注意事项同cpu。
五、上下文切换
上下文切换可以简单地理解为在调度前保护现场或恢复现场(保护或恢复CPU中的寄存器组),而xv6中的上下文特指以下寄存器:
- ra
- sp
- s0
- s1
- s2
- s3
- s4
- s5
- s6
- s7
- s8
- s9
- s10
- s11
函数swtch(使用汇编语言编写)负责内核线程切换的保护和恢复。它有两个参数第一个参数是old context,第二个参数是new context,它会将当前寄存器保存至old context中,而从new中加载寄存器并返回。
void swtch(struct context*, struct context*);
swtch不直接知道线程,只是保护和恢复寄存器组,该寄存器组称为上下文。
.globl swtch
swtch:
sd ra, 0(a0)
sd sp, 8(a0)
sd s0, 16(a0)
sd s1, 24(a0)
sd s2, 32(a0)
sd s3, 40(a0)
sd s4, 48(a0)
sd s5, 56(a0)
sd s6, 64(a0)
sd s7, 72(a0)
sd s8, 80(a0)
sd s9, 88(a0)
sd s10, 96(a0)
sd s11, 104(a0)