HW7:xv6 locking的简要说明。
DeadLock
struct spinlock lk;
initlock(&lk, "test lock");
acquire(&lk);
acquire(&lk);
要求我们用一句话说明,上述的代码出现的问题。
</br>答:由于在第一次acquire之后,并未release就进行了acquire操作,导致第二次的acuqire会永远获取不到锁,即死锁。
Interrupt in ide.c
在xv6的代码中,acquire函数会通过cli指令,屏蔽中断;release函数通过sti指令,将中断打开。但是,如果在ide.c的iderw函数中,我们在acquire函数的后面加上sti指令,而在release函数的前面加上cli指令,并重新编译内核,那么xv6就有可能boot失败,请找出原因。
答:
panic函数 </br>如下面的代码显示,getcallerpcs函数会将栈中存储的eip信息存放到pcs数组中,然后打印pcs数组。
void
panic(char *s)
{
int i;
uint pcs[10];
cli();
cons.locking = 0;
// use lapiccpunum so that we can call panic from mycpu()
cprintf("lapicid %d: panic: ", lapicid());
cprintf(s);
cprintf("\n");
getcallerpcs(&s, pcs);
for(i=0; i<10; i++)
cprintf(" %p", pcs[i]);
panicked = 1; // freeze other CPU
for(;;)
;
}
// Record the current call stack in pcs[] by following the %ebp chain.
void
getcallerpcs(void *v, uint pcs[])
{
uint *ebp;
int i;
ebp = (uint*)v - 2;
for(i = 0; i < 10; i++){
if(ebp == 0 || ebp < (uint*)KERNBASE || ebp == (uint*)0xffffffff)
break;
pcs[i] = ebp[1]; // saved %eip
ebp = (uint*)ebp[0]; // saved %ebp
}
for(; i < 10; i++)
pcs[i] = 0;
}
- 从下图中可以看出,xv6在
sched locks的地方被panic。

- 在
kernel.asm中,依次查找eip中的值,就可以查出调用栈了,调用栈为:</br>trapasm.S: trapret->proc.c: forkret->fs.c: iinit->fs.c: readsb->bio.c: bread->ide.c: iderw->trapasm.S: alltraps->trap.c: trap->proc.c: yield->proc.c: sched - 从调用栈中可以看出,在
ide.c:iderw函数中,调用到了acquire(&idelock)之后,由于sti指令被加入,所以中断又重新可以被触发,因此触发了trapams.S: alltraps,此时需要当前进程放弃CPU,因此调用到了yield函数,判断if(mycpu()->ncli != 1)的时候出错。
interrupts in file.c
- 将上述的
sti()和cli()去除,重新编译kernel,并保证kernel能够重新正常运行。 - 现在和上述的实验一样
- 在获得
file_table_lock的时候,通过sti,不再屏蔽中断。(file_table_lock用来保护文件描述符表,当我们在打开和关闭文件的时候,会修改这个文件描述符表。) - 在
file.c文件中的filealloc()中,acquire()函数被调用的后面加入sti(),然后在每一处调用到release()的前面,加入cli()。
- 在获得
-
重新编译
kernel,然后在qemu中将它启动,kernel并不会panic。 - 解释为什么我们对
file_table_lock和ide_lock做同样的操作,但是却出现了不同的表现: 因为从kernel对file_table_lock和ide_lock的使用情况来看,在xv6中没有中断处理函数会争ftable.lock保护的资源。自然也就不会acquire(&ftable.lock)。
xv6 lock implementation
Why does release() clear lk->pcs[0] and lk->cpu before clearing lk->locked? Why not wait until after?
答:因为在acquire的时候,会调用getcallerpcs来记录debug信息,即lk->pcs[0]和lk->cpu。因此当线程1在release的时候,如果lk->locked先被释放,会导致线程2通过getcallerpcs来修改lk->pcs[0]和lk->cpu,同时线程1自己也在修改lk->pcs[0]和lk->cpu,形成竞争条件。