本节,我们将探索中断与锁机制的一些互相作用的情况
弄清楚如果xv6内核执行下列这段代码,会发生什么?
struct spinlock lk;
initlock(&lk, “test lock”);
acquire(&lk);
acquire(&lk);
先阅读一下spinlock.c
acquire函数:
holding函数:
为什么会发生panic的情况?
答:根据代码逻辑可以知道,当前cpu已经获取了锁的情况下,再次请求上锁会导致panic。
acquire函数能够确保当前处理器在上锁后关闭中断,知道释放它持有的最后一把锁后才会开中断,看看如果我们在上锁后开中断会发生什么.在ide.c的iderw函数中在acquire()上锁且关中断后添加sti调用来开中断,在release()释放锁之前添加cli()调用来关中断,重新编译内核并在QEMU下启动,内核很可能会在启动后panic,如果没有,尝试多启动几次。
为什么内核会panic? 提示:在kernel.asm清单中查找堆栈跟踪(由panic打印的%eip值序列)很有用。
panic打印信息:
kernel.asm:
答:根据panic打印出来的eip值阅读kenerl.asm中的相关代码后,可以知道在iderw上锁且开中断之后,发生了中断,于是跳转到相应的处理函数ideintr中,而ideintr中在cpu持有锁的情况下再次请求上锁导致内核panic()。
删除你刚刚添加的cli()和sti(),重新编译内核,确保它能正常工作,现在看看在持有file_table_lock的情况下开中断会发生什么,该锁用于保护文件描述符表(进程调用open或close操作一个文件时,内核会修改此表)。在file.c的filealloc()函数调用acquire()上锁之后,添加sti()来开中断,在每个release()之前加上cli(),记得添加x86.h的头文件声明。重新编译并启动内核,它应该不会panic,修改代码并make qemu后确实是没有panic。
为什么内核没有panic?, 为什么file_table_lock和ide_lock在这方面的表现不同?查看哪些函数获取这些锁以及何时调用这些函数可能会对解决该问题有所帮助。(在qemu上,内核很少会因为filealloc()中添加的sti()产生panic。 如果内核崩溃,请确保从iderw中删除了sti()调用。 如果还是出现情况,并且唯一的sti()位于filealloc()中,请考虑一下为什么在实际硬件上不太可能出现这种情况。 )
答:file.c中使用到了file_table_lock锁的函数除了filealloc()就只有filedup和fileclose,产生panic的条件:同一cpu在持有锁的情况下再次请求上锁。ide_lock会发生panic的原因是iderw开中断后有对应的中断处理程序在已经上锁的情况下对ide_lock请求上锁,而file_table_lock因为没有对应的中断处理程序争抢file_table_lock资源,所以不会发生panic。(但是我觉得还有一种情况可能导致panic,开中断后可能导致线程切换,如果被切换的线程调用了filedup,filealloc或者fileclose请求上锁,由于切换前后占用的cpu没变,也可能会导致panic,只是这种可能性极低。)
在release函数中,为什么要把清除lk->pcs[0]和lk->cpu的工作放在释放锁之前而非释放锁之后?
答:考虑这样一种情况,如果先释放锁再做清除工作,线程一释放锁->线程二获取锁->线程二设置pcs[0],cpu->线程一清除pcs[0]和cpu。那么线程二的lk->pcs[0]和lk->cpu变量都没有被成设置应有的值,实际上lk->pcs[0]和lk->cpu也应看做是一种临界资源,在锁释放之前完成对它们的修改才能保证互斥访问。