Mit6.828 HW7:xv6 locking

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.ciderw函数中,我们在acquire函数的后面加上sti指令,而在release函数的前面加上cli指令,并重新编译内核,那么xv6就有可能boot失败,请找出原因。

答:

  1. 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;
}
  1. 从下图中可以看出,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_lockide_lock做同样的操作,但是却出现了不同的表现: 因为从kernelfile_table_lockide_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,形成竞争条件。

Tags: Mit6.828
Share: X (Twitter) Facebook LinkedIn