HW xv6 locks
在按照要求加上兩行程式碼之後, 有一定的可能出現了panic, 我測試10次出現4次
sched panic.
之後又試了幾次, 發現還有acquire panic
為什麼會panic?
panic: sched locks
先看sched panic出現的位置, 是在proc.c:378行
if(mycpu()->ncli != 1) panic("sched locks");
說明是mycpu()->ncli不等於1導致的panic, 那麼ncli是在哪裡修改的呢?
經過grep之後, 發現ncli只有在pushcli/popcli中被修改了, 在pushcli中++, 在popcli中–.
分析
檢視stack trace 的eip發現呼叫過程是這樣的:
版本一:
80103bc1: sched -> panic 80103d32: yield -> sched 80105933: trap-> yield 8010576f: alltraps-> trap 80100183: bread -> iderw 801013c5: readsb-> bread 801014bf: iinit -> readsb 801036c4: forkret-> iinit 80105772: trapret-> popal
版本二:
80103bc1: sched -> panic 80103d32: yield -> sched 80105933: trap-> yield 8010576f: alltraps-> trap 801021ba: bwrite-> idestart 80102aa2: write_head-> bwrite 80102b3e: recover_from_log-> write_head 801036d0: forkret-> initlog 80105772: trapret-> popal
版本三, 和版本一的稍微不同:
80103bc1: sched -> panic 80103d32: yield -> sched 80105933: trap-> yield 8010576f: alltraps-> trap **8010218e: iderw-> sleep** 80100183: bread -> iderw 801013c5: readsb-> bread 801014bf: iinit -> readsb 801036c4: forkret-> iinit 80105772: trapret-> popal
從上面三個版本中可以看出來, panic的出現都是由於陷入到了trap中, 在trap中呼叫了
yield, 檢視trap.c中的程式碼, 當時鍾tick訊號到來的時候會呼叫yield, 而yield程式碼如下:
void yield(void) { acquire(&ptable.lock);//DOC: yieldlock myproc()->state = RUNNABLE; sched(); release(&ptable.lock); }
看到這裡應該就清楚了, 這三個panic的版本都是在呼叫iderw, acquire之後, release之前, 被時鐘tick中斷, 然後進入到了yield中, 又依次acquire, 此時mycpu()->ncli == 2
,然後呼叫sched, (bang!
)panic.
panic: acquire
再看acquire panic的位置, 在spinlock.c:126
if(holding(lk)) panic("acquire");
當一個proc重複獲取鎖的時候會觸發這個panic
分析
panic: acquire 801043fd: acquire-> panic 80102063: ideintr-> acquire 80105995: trap-> ideintr 8010576f: alltrap-> trap 80100183: bread -> iderw 801019cf: readi -> bread 80106c96: loaduvm-> readi 80100b3f: exec-> loaduvm 801054e0: sys_exec-> exec 80104927: syscall-> sys_exec
其實從上面的的trace就能看出了, 先呼叫了iderw, 然後又呼叫了ideintr, 這兩個都呼叫
了acquire, 而且是對於同一個鎖, ideintr程式碼如下:
void ideintr(void) { struct buf *b; // First queued buffer is the active request. acquire(&idelock); //... }
Interrupts in file.c
按照上面說的加上sti和cli, 我竟然發現了panic…
同樣是panic: sched locks
80103bc1: sched -> panic 80103d32: yield -> sched 80105933: trap-> yield 8010576f: alltraps-> trap 8010518b: sys_open-> filealloc 80104927: syscall-> sys_open 80105aa9: trap-> syscall 8010576f: trapall-> trap
原因和上面分析的一樣, 在filealloc呼叫acquire之後開中斷, 接收到時鐘tick的中斷,
跑去執行trap了, 然後是yield, acquire, sched, panic!
教程上說出panic的可能很小啊.
On qemu there is a small chance that the kernel will panic with the extra sti() in filealloc().
這裡我懷疑是我之前哪個作業做錯了還是怎麼招, 我認為這個chance應該不小啊, 畢竟時鐘
tick可是1s好幾個G那麼多次, 如果真是錯了我會回來改的, 有人發現了也提醒我一下吧.
不過這次只有這一個panic, 而且和filealloc沒有什麼關係, 不像上面的, acquire的panic
是因為在中斷處理中又獲取同一個鎖.
所以kernel沒有panic的原因(強行解釋), 沒有在中斷處理程式中再次使用到共享的資料,
故沒有panic.
xv6 lock implementation
看下實現:
void release(struct spinlock *lk) { if(!holding(lk)) panic("release"); lk->pcs[0] = 0;// 為什麼這兩行在clear lk->locked之前 lk->cpu = 0; __sync_synchronize(); asm volatile("movl $0, %0" : "+m" (lk->locked) : ); popcli(); }
如果清零在前, 可能會出現下面的情況:
|CPU1CPU2 | |lk->locked=0 | |獲取鎖, 設定lk->pcs, lk->cpu |lk->pcs[0]=0 |lk->cpu=0 v