delphifirst
delphifirst

Reputation: 1857

Why xv6 scheduler calls sti() in the begining of every loop?

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.

Upvotes: 3

Views: 2604

Answers (2)

Jet Blue
Jet Blue

Reputation: 5281

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

Upvotes: 1

Benjamin Peterson
Benjamin Peterson

Reputation: 20520

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

Upvotes: 1

Related Questions