3

The companion book says

The reason to enable interrupts periodically on an idling CPU is that there might be no RUNNABLE process because processes (e.g., the shell) are waiting for I/O; if the scheduler left interrupts disabled all the time, the I/O would never arrive.

But I think we just need to call sti() once before the outter for-loop, since everytime we release ptable.lock, the interrupts are enabled again.

delphifirst
  • 1,781
  • 1
  • 14
  • 23
  • Maybe you should try removing the repeated `sti` and see if the scheduler hangs or continues normally. – smac89 Nov 26 '16 at 05:28

2 Answers2

1

It's possible that schedule() is called with interrupts disabled, in which case releasing the ptable spinlock will not reenable them.

Benjamin Peterson
  • 19,297
  • 6
  • 32
  • 39
  • Yes, that's right. But I think we only need to enable interrupts at the begining of scheduler(). Why do we need to do this repeatedly? Can you give me a scenario when it is needed? Many thanks. – delphifirst May 08 '15 at 11:22
1

If you look at the code for releasing a lock, you will see that it does not explicitly enable interrupts. Instead it uses the function popcli.

void release ( struct spinlock* lk )
{
    ...
    popcli();  // enable interrupts
}

The function popcli does not always enable interrupts. It is used in tandem with pushcli to track nesting level. "Pushcli/popcli are like cli/sti except that they are matched: it takes two popcli to undo two 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
    }
}

Whereas popcli sometimes enables interrupts, pushcli always disables interrupts.

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;
}

By explicitly calling sti, the scheduler overrides whatever the current push/popcli state is. I think this provides the brief window desired to allow an IO interrupt to occur. I.e. the period of time between the call to sti and the call to cli (via 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 );
    }
}
Jet Blue
  • 5,109
  • 7
  • 36
  • 48