目录

MIT 6.828 课程 | HW7-xv6 locking

在这个HW中,我们将探索interrupt和lock之间的相互作用。首先我们来看一下spinlock.cspinlock.h中的一些函数和定义

1
2
3
4
struct spinlock;
initlock();
acquire();
release()

这些主要是lock的具体实现,比如struct spinlock是lock的结构体,initlock()是初始化一个锁,acquire()是获得一个锁,release()是释放一个lock。

Don’t do this

1
2
3
4
struct spinlock lk;
initlock(&lk, "test lock");
acquire(&lk);
acquire(&lk);

上面这段操作,连续两个acquire(),将会造成panic。

Interrupts in ide.c

acquire()函数通过cli指令(使用 pushchi())来保证当前的处理器的中断是关闭了的,中断关闭状态持续到处理器的最后一个lock被release掉(通过popsti()来使用sti指令)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Acquire the lock.
// Loops (spins) until the lock is acquired.
// Holding a lock for a long time may cause
// other CPUs to waste time spinning to acquire it.
void
acquire(struct spinlock *lk)
{
  pushcli(); // disable interrupts to avoid deadlock.
  if(holding(lk))
    panic("acquire");

   ......
}

// Release the lock.
void
release(struct spinlock *lk)
{
  if(!holding(lk))
    panic("release");

  lk->pcs[0] = 0;
  lk->cpu = 0;
  ......
  // Release the lock, equivalent to lk->locked = 0.
  asm volatile("movl $0, %0" : "+m" (lk->locked) : );

  popcli();
}

假如我们在持有ide lock的时候,打开了interrupt会发生什么勒?我们在ide.ciderw()函数中,在acquire后面调用 sti(),并且在release之前调用cli()sti()函数会打开interrupt,而cli()函数会关闭中断)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void
iderw(struct buf *b)
{
  ......

  acquire(&idelock);  //DOC:acquire-lock
  sti();
  ......
  cli();
  release(&idelock);
}

重新编译kernel和启动QEMU,有些时候kernel在启动之后会panic,多尝试几次之后直到kernel正常启动。

https://img.dawnguo.cn/OS/MIT6828/HW7_0.jpg

有几次kernel的启动会panic,只要是因为我们在acquire关闭中断之后,又打开了中断,这样子会陷入死锁。比如在启动过程中,发生了中断,而这个中断又会去申请一个锁,然而锁又被其他所持有,但是其他又在等待中断处理结束,那么就陷入死锁了。

Interrupts in file.c

将你添加在ide.c中添加的sti()cli()移掉。下面我们来看一下当hold file_table_lock的时候,打开中断会发生什么。这lock是用来保护file descriptor table的,当一个应用程序open或close一个文件的时候kernel会修改这个表。那么下面我们在file.cfilealloc()函数中,在acquire()后面调用call(),同时在每一个re

lease()之前调用cli(),并且我们也需要把x86.h这个头文件include进去

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
......
#include "x86.h"
......
// Allocate a file structure.
struct file*
filealloc(void)
{
  struct file *f;

  acquire(&ftable.lock);
  sti();
  for(f = ftable.file; f < ftable.file + NFILE; f++){
    if(f->ref == 0){
      f->ref = 1;
      cli();
      release(&ftable.lock);
      return f;
    }
  }
  cli();
  release(&ftable.lock);
  return 0;
}

之后重新编译kernel并运行,你会发现启动多少次后都不会panic。这个主要是因为filealloc()在alloc的时候,时间很短,所以很难发生死锁。

xv6 lock implementation

为什么release()函数要先清理掉lk->pcs[0]lk->cpu,之后再清理掉lk->locked

因为假如先清理掉lk->locked,那么lock就没了,新的进程可能就会在lk->pcs[0]lk->cpu被清零之前获得lock,但是此时的lk->cpulk->pcs[0]还是之前的那个,那么就会导致不一致。