在这个HW中,我们将探索interrupt和lock之间的相互作用。首先我们来看一下spinlock.c和spinlock.h中的一些函数和定义
struct spinlock;
initlock();
acquire();
release()
这些主要是lock的具体实现,比如struct spinlock是lock的结构体,initlock()是初始化一个锁,acquire()是获得一个锁,release()是释放一个lock。
Don't do this
struct spinlock lk;
initlock(&lk, "test lock");
acquire(&lk);
acquire(&lk);
上面这段操作,连续两个acquire(),将会造成panic。
Interrupts in ide.c
acquire()函数通过cli指令(使用 pushchi())来保证当前的处理器的中断是关闭了的,中断关闭状态持续到处理器的最后一个lock被release掉(通过popsti()来使用sti指令)。
// 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.c中iderw()函数中,在acquire后面调用 sti(),并且在release之前调用cli()(sti()函数会打开interrupt,而cli()函数会关闭中断)。
void
iderw(struct buf *b)
{
......
acquire(&idelock); //DOC:acquire-lock
sti();
......
cli();
release(&idelock);
}
重新编译kernel和启动QEMU,有些时候kernel在启动之后会panic,多尝试几次之后直到kernel正常启动。

有几次kernel的启动会panic,只要是因为我们在acquire关闭中断之后,又打开了中断,这样子会陷入死锁。比如在启动过程中,发生了中断,而这个中断又会去申请一个锁,然而锁又被其他所持有,但是其他又在等待中断处理结束,那么就陷入死锁了。
Interrupts in file.c
将你添加在ide.c中添加的sti()和cli()移掉。下面我们来看一下当hold file_table_lock的时候,打开中断会发生什么。这lock是用来保护file descriptor table的,当一个应用程序open或close一个文件的时候kernel会修改这个表。那么下面我们在file.c的filealloc()函数中,在acquire()后面调用call(),同时在每一个re
lease()之前调用cli(),并且我们也需要把x86.h这个头文件include进去
......
#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->cpu和lk->pcs[0]还是之前的那个,那么就会导致不一致。