world leader in high performance signal processing
Trace: » task_switching

Task Switching

The code responsible for stopping one task and resuming another is containd in the file linux-2.6.x/kernel/sched.

This code is quite concerned with the job of switching memory maps between the old task and the new.

NOTE the unlikely macros alter the way the compiler deals with the if statement optimization. If a statement is unlikeky the fast path through the code will be to bypass the if statement. This helps greatly to prevent instruction pipeline stalls when a code branch causes a change in the code flow at assembley level.

The actual task switch is done in the switch_to function or macro. This is found in linux-2.6.x/include/asm/system.h

static inline
task_t * context_switch(runqueue_t *rq, task_t *prev, task_t *next)
{
        struct mm_struct *mm = next->mm;
        struct mm_struct *oldmm = prev->active_mm;
 
        if (unlikely(!mm)) {
                next->active_mm = oldmm;
                atomic_inc(&oldmm->mm_count);
                enter_lazy_tlb(oldmm, next);
        } else
                switch_mm(oldmm, mm, next);
 
        if (unlikely(!prev->mm)) {
                prev->active_mm = NULL;
                WARN_ON(rq->prev_mm);
                rq->prev_mm = oldmm;
        }
 
        /* Here we just switch the register state and the stack. */
        switch_to(prev, next, prev);
 
        return prev;
}

And here is the switch_to macro

asmlinkage void resume(void);
#define switch_to(prev,next,last) { \
  void *_last;                                                          \
  __asm__ __volatile__(                                                 \
                        "r0 = %1;\n\t"                                  \
                        "r1 = %2;\n\t"                                  \
                        "call resume;\n\t"                              \
                        "%0 = r0;\n\t"                                  \
                       : "=d" (_last)                                   \
                       : "d" (prev),                                    \
                         "d" (next)                                     \
                       : "CC", "R0", "R1", "P0", "P1");                 \
  (last) = _last;                                                       \
}

All the work is done in the resume code from linux-2.6.x/arch/blackfin/mach-common/entry.S

This code saves the current context and then recovers the context of the new task. After that time a rts will return the code to the new task since the program counter and the stack pointer have all been set up to point to the ones saved when the new task was switched from.

This code does do a secondary jump to dummy new task to allow the stack and frame pointers to be recovered.

ENTRY(resume)
        /*
         * Beware - when entering resume, prev (the current task) is
         * in r0, next (the new task) is in r1.
         */
        p0 = r0;
        p1 = r1;
        [--sp] = rets;
        [--sp] = fp;
 
        /* save usp */
        p2 = usp;
        [p0+(TASK_THREAD+THREAD_USP)] = p2;
 
        /* save current kernel stack pointer */
        [p0+(TASK_THREAD+THREAD_KSP)] = sp;
 
        /* save program counter */
        r1.l = new_old_task;
        r1.h = new_old_task;
        [p0+(TASK_THREAD+THREAD_PC)] = r1;
 
        /* restore the kernel stack pointer */
        sp = [p1+(TASK_THREAD+THREAD_KSP)];
 
        /* restore user stack pointer */
        p0 = [p1+(TASK_THREAD+THREAD_USP)];
        usp = p0;
 
        /* restore pc */
        p0 = [p1+(TASK_THREAD+THREAD_PC)];
        jump (p0);
 
        /*
         * Following code actually lands up in a new (old) task.
         */
 
new_old_task:
        fp = [sp++];
        rets = [sp++];
 
        /*
         * When we come out of resume, r0 carries "old" task, becuase we are
         * in "new" task.
         */
        rts;

What about Memory Switching

The switch_mm code does the work of switching between the old task and the new task's memory structures We'll find that code in this file linux-2.6.x/include/asm/mmu_context.h

static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, struct task_
struct *tsk)
{
}

No surprise, the blackfin has no memory management unit and hence no code required to set the system up to accomodate the new task's memory area.

Expect to see a lot more code in this function on other architectures.