为何xv6的scheduler()需重新获取ptable.lock?sleep()已持有该锁
关于xv6中sleep()与scheduler()重复获取ptable.lock的疑问与解答
问题描述
在学习xv6的sleep/wakeup()机制时,针对ptable.lock的使用有两个核心疑问:
- sleep()执行过程中会获取ptable.lock,随后调用sched()切换到调度器,此时按道理ptable.lock应该处于持有状态,但scheduler()的循环里却再次尝试获取该锁,为什么这个获取操作能成功?
- 调度器为什么需要重复执行ptable.lock的获取操作?
相关代码片段
sleep()函数关键代码
void sleep(void *chan, struct spinlock *lk) { struct proc *p = myproc(); ... if(lk != &ptable.lock){ //DOC: sleeplock0 acquire(&ptable.lock); // <---1 获取ptable.lock release(lk); } // 标记进程为睡眠状态 p->chan = chan; p->state = SLEEPING; sched(); // <---2 切换到调度器 // 清理工作 p->chan = 0; // 重新获取原锁 if(lk != &ptable.lock){ //DOC: sleeplock2 release(&ptable.lock); acquire(lk); } }
scheduler()函数关键代码
void scheduler(void) { struct proc *p; struct cpu *c = mycpu(); c->proc = 0; for(;;){ ... // 遍历进程表寻找可运行进程 acquire(&ptable.lock); // <---3 再次尝试获取ptable.lock ...
解答
1. 为什么调度器能成功获取ptable.lock?
核心原因是sched()函数在切换上下文前会自动释放当前进程持有的所有自旋锁。
xv6的自旋锁设计里,每个CPU的struct cpu结构体维护了一个lock_depth字段,用来记录当前进程持有自旋锁的数量。当进程调用sched()准备切换到调度器时,sched()内部会先调用release()把当前进程持有的所有自旋锁(包括ptable.lock)全部释放,直到lock_depth归0,之后才会调用swtch()完成上下文切换。
所以当调度器执行到acquire(&ptable.lock)时,该锁已经处于未被持有的状态,自然能成功获取。
2. 为什么调度器需要重复获取ptable.lock?
进程表(ptable)是全局共享资源,所有CPU的调度器都可能访问它。每次进入调度循环时重新获取ptable.lock,是为了:
- 保护进程表的并发访问:防止多个CPU的调度器同时遍历或修改进程表,避免竞态条件(比如一个调度器正在修改进程状态,另一个调度器同时读取该进程)。
- 配合锁的释放逻辑:调度器在找到可运行的进程后,会先释放ptable.lock,再切换到该进程执行。所以下一次循环时,锁已经被释放,必须重新获取才能安全地访问进程表。
内容的提问来源于stack exchange,提问作者Jacky Wang
相关产品推荐
相关产品推荐

