※ この記事はまだ勉強中のため、いろいろ間違いがあるかもしれません。
さて、switchuvmの中で何が起きているのか見ていこう。
<FunctionCall 59150135: switchuvm(0x8010a994)> <FunctionCall 59150142: pushcli(0x80107538)> <FunctionCall 59150149: disableinterrupt(0x8010717c)> <FunctionCall 59150155: is_interruptible(0x8010713c)> <FunctionCall 59150161: read_cop0_status(0x801070e0)> <Return 59150174: read_cop0_status> <Return 59150184: is_interruptible> <Return 59150193: disableinterrupt> <Return 59150208: pushcli> <FunctionCall 59150226: tlbwi(0x8010a324)> <Return 59150254: tlbwi> <FunctionCall 59150258: popcli(0x801075a4)> <FunctionCall 59150264: is_interruptible(0x8010713c)> <FunctionCall 59150270: read_cop0_status(0x801070e0)> <Return 59150283: read_cop0_status> <Return 59150293: is_interruptible> <Return 59150315: popcli> <Return 59150322: switchuvm> <FunctionCall 59150361: finalizefork(0x80106bc8)>
switchuvmが終わって、次にFinalizeforkという関数が呼ばれているが、これは何だろう?少なくとも、この関数はC言語中には存在していなかった。 実際には、schedulerで最初のプロセスが選ばれて、finalizeforkという関数が呼ばれている。
finalizeforkは、forkretという関数から呼ばれているものだ。
p->context->eip = (uint)forkret;
MIPS用の実装では、以下のようになっている:
forkret: jal finalizefork nop j trapret nop
従って、switchuvmから戻ってくると、まずはforkret --> finalizeforkとなる。 finalizeforkでは、最初のプロセス起動である場合、initlogでログファイルの初期化を始める。
finalizefork(void) { static int first = 1; // Still holding ptable.lock from scheduler. release(&ptable.lock); if (first) { // Some initialization functions must be run in the context // of a regular process (e.g., they call sleep), and thus cannot // be run from main(). first = 0; initlog(); } } // log.c initlog(void) { if (sizeof(struct logheader) >= BSIZE) panic("initlog: too big logheader"); struct superblock sb; initlock(&log.lock, "log"); readsb(ROOTDEV, &sb); log.start = sb.size - sb.nlog; log.size = sb.nlog; log.dev = ROOTDEV; recover_from_log(); }
readsbで、ルートデバイスのスーパブロックを読みにいっているらしい。
// fs.c // Read the super block. void readsb(int dev, struct superblock *sb) { struct buf *bp; bp = bread(dev, 1); memmove(sb, bp->data, sizeof(*sb)); brelse(bp); } // bio.c struct buf* bread(uint dev, uint sector) { struct buf *b; b = bget(dev, sector); if(!(b->flags & B_VALID)) iderw(b); return b; } // ide.c void iderw(struct buf *b) { struct buf **pp; if(!(b->flags & B_BUSY)) panic("iderw: buf not busy"); if((b->flags & (B_VALID|B_DIRTY)) == B_VALID) panic("iderw: nothing to do"); if(b->dev != 0 && !havedisk1) panic("iderw: ide disk 1 not present"); acquire(&idelock); //DOC:acquire-lock // Append b to idequeue. b->qnext = 0; for(pp=&idequeue; *pp; pp=&(*pp)->qnext) //DOC:insert-queue ; *pp = b; // Start disk if necessary. if(idequeue == b) idestart(b); // Wait for request to finish. while((b->flags & (B_VALID|B_DIRTY)) != B_VALID){ sleep(b, &idelock); } release(&idelock); }
ここで、デッドロックとなる。よく考えてみれば、IEDからの読み込み作業を開始して、IDEの読み込み完了の通知は割り込みを通じて来るんだった。 それがシミュレータに実装されていない。だからデッドロックしたのか。 では、それを実装していく。