When learning about the sleep/wakeup() mechanism in xv6 (x86 version, .828/2018/xv6.html), I have a question regarding the usage the ptable.lock in sleep().
- sleep() acquire
ptable.lock
- It then calls sched() to switch to scheduler, at this point,
ptable.lock
should be held (field.locked
should be 1) - However, in scheduler, inside the for loop, it first attempt to acquire
ptable.lock
, even though the lock should be already held, why does it need to acquire the lock again? and why does the acquisition can be succeed?
void sleep(void *chan, struct spinlock *lk)
{
struct proc *p = myproc();
...
if(lk != &ptable.lock){ //DOC: sleeplock0
acquire(&ptable.lock); // <---1
release(lk);
}
// Go to sleep.
p->chan = chan;
p->state = SLEEPING;
sched(); // <--- 2
// Tidy up.
p->chan = 0;
// Reacquire original lock.
if(lk != &ptable.lock){ //DOC: sleeplock2
release(&ptable.lock);
acquire(lk);
}
}
void scheduler(void) {
struct proc *p;
struct cpu *c = mycpu();
c->proc = 0;
for(;;){
...
// Loop over process table looking for process to run.
acquire(&ptable.lock); // <--- 3
...
When learning about the sleep/wakeup() mechanism in xv6 (x86 version, https://pdos.csail.mit.edu/6.828/2018/xv6.html), I have a question regarding the usage the ptable.lock in sleep().
- sleep() acquire
ptable.lock
- It then calls sched() to switch to scheduler, at this point,
ptable.lock
should be held (field.locked
should be 1) - However, in scheduler, inside the for loop, it first attempt to acquire
ptable.lock
, even though the lock should be already held, why does it need to acquire the lock again? and why does the acquisition can be succeed?
void sleep(void *chan, struct spinlock *lk)
{
struct proc *p = myproc();
...
if(lk != &ptable.lock){ //DOC: sleeplock0
acquire(&ptable.lock); // <---1
release(lk);
}
// Go to sleep.
p->chan = chan;
p->state = SLEEPING;
sched(); // <--- 2
// Tidy up.
p->chan = 0;
// Reacquire original lock.
if(lk != &ptable.lock){ //DOC: sleeplock2
release(&ptable.lock);
acquire(lk);
}
}
void scheduler(void) {
struct proc *p;
struct cpu *c = mycpu();
c->proc = 0;
for(;;){
...
// Loop over process table looking for process to run.
acquire(&ptable.lock); // <--- 3
...
Share
Improve this question
edited Apr 11 at 7:55
Jacky Wang
asked Mar 2 at 4:15
Jacky WangJacky Wang
3,5203 gold badges29 silver badges48 bronze badges
2 Answers
Reset to default 1I think you've mixed up two functions with similar names. sleep()
doesn't call scheduler()
, it calls sched()
. And you can see that sched()
doesn't acquire the ptable lock; rather, it asserts that it is already held.
The reason scheduler()
needs to acquire ptable.lock
again, even though sleep()
already had it, is because of what happens during the context switch.
When a process calls sleep()
, it first acquires ptable.lock
(if it wasn’t already held), marks itself as SLEEPING
, and then calls sched()
. Inside sched()
, the process gives up the CPU, and during this switch, ptable.lock
is released.
Now, when scheduler()
runs, it needs to look at the process table to find the next runnable process. But since the lock was released when the previous process switched out, scheduler()
has to acquire it again before scanning the process table.
So, even though ptable.lock
was held earlier in sleep()
, it gets released when the process is switched out, allowing scheduler()
to successfully acquire it again. This prevents race conditions and ensures the process table isn’t modified by multiple processes at the same time.