7.调度相关
一.基础
用户线程状态
- RUNNING,线程当前正在某个CPU上运行
- RUNABLE,线程还没有在某个CPU上运行,但是一旦有空闲的CPU就可以运行
- SLEEPING,这节课不介绍,
一. 定时器中断线程切换
防止个别程序长时间占有线程导致其他线程饿死,通过定时器中断定时释放线程资源
定时器中断是由硬件发起的
一.线程切换流程
P0用户线程-->P0内核线程-->调度器线程-->P1内核线程-->P1用户线程
调度器线程是每个CPU核独有的,也是一个软件,其实就是proc.c\scheduler()
函数,保存在cpu->context
中
1. P0用户线程-->P0内核线程
计时器发出中断,用户线程跳转到内核线程
跳转时,将用户线程运行时信息(寄存器,pc等)保存在trapframe
中
2. P0内核线程-->CPU核对应的调度器线程
计时器中断,所以会调用proc.c\yield()
void yield(void)
{
struct proc *p = myproc();
// 这里必须加锁,因为需要将进程的状态修改为RUNNABLE
// 且在修改后的一段时间,进程实际还在执行,如果不加锁
// 在修改进程状态后,其他调度器可能就会开始运行此进程
// 从而就会发生一个进程在两个CPU上同时运行的错误状况
// 这里加的锁,在scheduler()中才会释放
acquire(&p->lock);
p->state = RUNNABLE;
sched();
// 这里释放的不是上面加的锁
release(&p->lock);
}
之后是proc.c\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(struct context *old, struct context *new)
// 将当前内核线程的状态保存到p->context中,
// 然后恢复调度器线程,mycpu()->context保存
// 的就是调度器线程信息
swtch(&p->context, &mycpu()->context);
mycpu()->intena = intena;
}
最后使用swtch.S
函数切换到调度器线程
mycpu()->context
中的ra指向scheduler()
函数,切换完成后,CPU将跳转到scheduler()
函数中
.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)
ld ra, 0(a1)
ld sp, 8(a1)
ld s0, 16(a1)
ld s1, 24(a1)
ld s2, 32(a1)
ld s3, 40(a1)
ld s4, 48(a1)
ld s5, 56(a1)
ld s6, 64(a1)
ld s7, 72(a1)
ld s8, 80(a1)
ld s9, 88(a1)
ld s10, 96(a1)
ld s11, 104(a1)
ret
注:这里只需要保存Callee寄存器,因为按照约定,只有Callee Saved寄存器在函数调用的时候会保存和恢复
3. 调度器线程-->P1内核线程
proc.c\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();
for(p = proc; p < &proc[NPROC]; p++) {
//这里加的锁在yield()中释放
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->context, &p->context);
// switch.S会跳转到这里继续执行
c->proc = 0;
}
//这里释放的是yield()加的锁
release(&p->lock);
}
}
}
4.P1内核线程-->P1用户线程
就是正常的中断返回,回到用户线程继续执行
线程切换工作完成
二.首次线程切换
二.Sleep & Wake up
-
在线程切换的过程中需要一直持有p->lock
-
XV6中,不允许进程在执行switch函数的过程中,持有任何其他的锁,因为可能会影响其他进程的后续执行
例如获取锁的函数
acquire()
需要关闭中断,因为不关闭中断,在获取到锁后,发生了中断,而中断切换到的程序运行也需要这把锁,那就会发生死锁