配套书说
在空闲的 CPU 上定期启用中断的原因是可能没有 RUNNABLE 进程,因为进程(例如,shell)正在等待 I/O。如果调度程序一直禁用中断,则 I/O 将永远不会到达。
但我认为我们只需要在外部 for 循环之前调用 sti() 一次,因为每次我们释放 ptable.lock 时,都会再次启用中断。
配套书说
在空闲的 CPU 上定期启用中断的原因是可能没有 RUNNABLE 进程,因为进程(例如,shell)正在等待 I/O。如果调度程序一直禁用中断,则 I/O 将永远不会到达。
但我认为我们只需要在外部 for 循环之前调用 sti() 一次,因为每次我们释放 ptable.lock 时,都会再次启用中断。
可能会schedule()
在禁用中断的情况下调用它,在这种情况下,释放 ptable 自旋锁不会重新启用它们。
如果您查看释放锁的代码,您会发现它没有显式启用中断。相反,它使用函数popcli
。
void release ( struct spinlock* lk )
{
...
popcli(); // enable interrupts
}
该函数popcli
并不总是启用中断。它与pushcli
跟踪嵌套级别一起使用。“Pushcli/popcli 与 cli/sti 类似,只是它们是匹配的:需要两个 popcli 才能撤消两个 pushcli”。1
void popcli ( void )
{
// If interrupts are enabled, panic...
if ( readeflags() & FL_IF )
{
panic( "popcli: interruptible" );
}
// Track depth of cli nesting
mycpu()->ncli -= 1;
// Popped more than were pushed...
if ( mycpu()->ncli < 0 )
{
panic( "popcli" );
}
// Reached outermost, so restore interrupt state
if ( mycpu()->ncli == 0 && mycpu()->intena )
{
sti(); // enable interrupts
}
}
而popcli
有时启用中断,pushcli
总是禁用中断。
void pushcli ( void )
{
int eflags;
eflags = readeflags();
// Disable interrupts
cli();
// Save interrupt state at start of outermost
if ( mycpu()->ncli == 0 )
{
mycpu()->intena = eflags & FL_IF;
}
// Track depth of cli nesting
mycpu()->ncli += 1;
}
通过显式调用sti
,调度程序会覆盖当前的 push/popcli 状态。我认为这提供了允许 IO 中断发生所需的简短窗口。sti
即调用和调用之间的时间段cli
(通过acquire
-> pushcli
-> cli
)。
void scheduler ( void )
{
...
for ( ;; )
{
// Enable interrupts on this processor.
sti();
// Acquire process table lock
acquire( &ptable.lock );
// Loop over process table looking for process to run.
for ( p = ptable.proc; p < &ptable.proc[ NPROC ]; p += 1 )
{
...
}
// Release process table lock
release( &ptable.lock );
}
}