*
**********************************************************************/
void Sched::schedule(void) {
Task * pOldTask;
Task * pNewTask;
if (state != Started) return;
//
// Postpone rescheduling until all interrupts are completed.
//
if (interruptLevel != 0) {
bSchedule = 1;
return;
}
//
// If there is a higher-priority ready task, switch to it.
//
if (pRunningTask != readyList.pTop) {
pOldTask = pRunningTask;
pNewTask = readyList.pTop;
pNewTask->state = Running;
pRunningTask = pNewTask;
if (pOldTask == NULL) {
contextSwitch(NULL, &pNewTask->context);
} else {
pOldTask->state = Ready;
contextSwitch(&pOldTask->context, &pNewTask->context);
}
}
} /* schedule() */
As you can see from this code, there are two situations during which the scheduler will not initiate a context switch. The first is if multitasking has not been enabled. This is necessary because application programmers sometimes want to create some or all of their tasks before actually starting the scheduler. In that case, the application's main routine would look like the following one. Each time a Task object is created, the scheduler is invoked. [21] Remember, task creation is one of our scheduling points. If the scheduler has been started, there is also a possibility that the new task will be the highest priority ready task.
However, because schedule checks the value of state to ensure that multitasking has been started, no context switches will occur until after start is called.
#include "adeos.h"
void taskAfunction(void);
void taskBfunction(void);
/*
* Create two tasks, each with its own unique function and priority.
*/
Task taskA(taskAfunction, 150, 256);
Task taskB(taskBfunction, 200, 256);
/*********************************************************************
*
* Function: main()
*
* Description: This is what an application program might look like
* if ADEOS were used as the operating system. This
* function is responsible for starting the operating
* system only.
*
* Notes: Any code placed after the call to os.start() will
* never be executed. This is because main() is not a
* task, so it does not get a chance to run once the
* scheduler is started.
*
* Returns: This function will never return!
*
*********************************************************************/
void main(void) {
os.start();
// This point will never be reached.
} /* main() */
Because this is an important piece of code, let me reiterate what you are looking at. This is an example of the application code you might write as a user of ADEOS. You begin by including the header file adeos.h and declaring your tasks. After you declare the tasks and call os.start , the task functions taskAfunction and taskBfunction will begin to execute (in pseudoparallel). Of course, taskB has the highest priority of the two (200), so it will get to run first. However, as soon as it relinquishes control of the processor for any reason, the other task will have a chance to run as well.
The other situation in which the ADEOS scheduler will not perform a context switch is during interrupt processing. The operating system tracks the nesting level of the current interrupt service routine and allows context switches only if the nesting level is zero. If the scheduler is called from an ISR (as it is during the timer tick), the bSchedule flag is set to indicate that the scheduler should be called again as soon as the outermost interrupt handler exits. This delayed scheduling speeds up interrupt response times throughout the system.
the actual process of changing from one task to another is called a context switch. Because contexts are processor-specific, so is the code that implements the context switch. That means it must always be written in assembly language. Rather than show you the 80x86-specific assembly code that I used in ADEOS, I'll show the context switch routine in a C-like pseudocode:
void contextSwitch(PContext pOldContext, PContext pNewContext) {
if (saveContext(pOldContext)) {
//
// Restore new context only on a nonzero exit from saveContext().
//
restoreContext(pNewContext);
// This line is never executed!
}
// Instead, the restored task continues to execute at this point.
}
The contextSwitch routine is actually invoked by the scheduler, which is in turn called from one of the operating system calls that disables interrupts. So it is not necessary to disable interrupts here. In addition, because the operating system call that invoked the scheduler is written in a high-level language, most of the running task's registers have already been saved onto its local stack. That reduces the amount of work that needs to be done by the routines saveContext and restoreContext . They need only worry about saving the instruction pointer, stack pointer, and flags.
The actual behavior of contextSwitch at runtime is difficult to see simply by looking at the previous code. Most software developers think serially, assuming that each line of code will be executed immediately following the previous one. However, this code is actually executed two times, in pseudoparallel. When one task (the new task) changes to the running state, another (the old task) must simultaneously go back to the ready state. Imagine what the new task sees when it is restored inside the restoreContext code. No matter what the new task was doing before, it always wakes up inside the saveContext code — because that's where its instruction pointer was saved.
How does the new task know whether it is coming out of saveContext for the first time (i.e., in the process of going to sleep) or the second time (in the process of waking up)? It definitely does need to know the difference, so I've had to implement saveContext in a slightly sneaky way. Rather than saving the precise current instruction pointer, saveContext actually saves an address a few instructions ahead. That way, when the saved context is restored, execution continues from a different point in the saveContext routine. This also makes it possible for saveContext to return different values: nonzero when the task goes to sleep and zero when the task wakes up. The contextSwitch routine uses this return value to decide whether to call restoreContext . If contextSwitch did not perform this check, the code associated with the new task would never get to execute.
I know this can be a complicated sequence of events to follow, so I've illustrated the whole process in Figure 8-3.
Читать дальше