Listing for the Task Dispatcher in August DDJ
/********************************************************
DISPATCH - A Task Dispatcher.
A program to manage multiple tasks via a ready queue
and multiple delays via a delay queue. Runs one task at
a time. Tasks coming off the delay queue are put on the
ready queue. Delay queue is ticked from timer interrupt.
Ron Kreymborg
11-Apr-99
********************************************************/
#define TOTALTASKS 10 // set for your application
void InitMulti(void);
int QueTask(void (*pt)());
int QueDelay(void (*pt)(), int delay);
void DecrementDelay(void);
void Dispatch(void);
void ReRunMe(int delay);
#define BusyBit 0x01 // task is busy
#define ReadyBit 0x02 // task is ready
#define DelayBit 0x04 // task is delayed
static void DoQueTask(void);
static void DoQueDelay(void);
static void RunTask(void);
static void GetNewTask(void);
static void DoDelay(int delay);
static struct {
int status; // task status
int delay; // delay in ticks
void (*pntr)(); // pointer to task
int next; // linked list
} task[TOTALTASKS+1];
static int RunningTask;
static int TaskHead, TailTask;
static int Status, NewTask;
static int *DelayHead;
static int NewDelay;
static void (*Tpntr)();
//**********************************************************
// Initialise the data structure. Must be called before
// first use.
void InitMulti(void) {
int i;
for (i=0; i<=TOTALTASKS; i++) {
task[i].status = 0;
task[i].pntr = 0;
task[i].next = 0;
}
DelayHead = &task[0].next;
}
//**********************************************************
// Put a task on the tail of the ready list. Call with
// a pointer to the task as argument.
int QueTask(void (*pntr)()) {
Tpntr = pntr; // set task pointer
GetNewTask(); // set NewTask
DoQueTask(); // put new task on queue
return Status; // return status
}
//**********************************************************
// Put a task in the delay queue such that it will come
// off after clock ticks. Call with a pointer
// to the task as argument.
int QueDelay(void (*pntr)(), int delay) {
Tpntr = pntr; // set task pointer
DoDelay(delay); // set NewDelay
GetNewTask(); // set NewTask
if (NewDelay)
DoQueDelay(); // put task on delay queue
else
DoQueTask(); // if zero just put on ready queue
return Status; // return status
}
//**********************************************************
// Run the task at the head of the ready queue, or just
// return.
void Dispatch(void) {
if (TaskHead) {
RunningTask = TaskHead;
TaskHead = task[RunningTask].next; // head is now next
if (TaskHead == 0) // ensure tail is correct
TailTask = 0;
RunTask(); // run the task
}
}
//**********************************************************
// Call just before the running task returns to ensure the
// task is re-run or, if is non-zero, when a task
// must be re-run after a delay.
void ReRunMe(int delay) {
DoDelay(delay); // set NewDelay
NewTask = RunningTask; // set NewTask
}
//**********************************************************
// Call from tick timer interrupt routine. Assumes interrupts
// are off. Decrements the delay of the task at the head of
// the delay queue. If it goes to zero takes it off the delay
// queue and puts it on the ready queue. Continues to do this
// until a non-zero delay is found.
void DecrementDelay(void) {
if (*DelayHead) {
task[*DelayHead].delay--; // decrement head delay
while (*DelayHead && task[*DelayHead].delay == 0) {
NewTask = *DelayHead; // set NewTask
*DelayHead = task[*DelayHead].next; // set head to next
task[NewTask].status &= ~(BusyBit | DelayBit);
Tpntr = task[NewTask].pntr; // set task pointer
DoQueTask(); // copy to ready list
}
}
}
//**********************************************************
// Private functions begin.
//**********************************************************
// Take the task pointer and insert it in the data
// structure at . Link from the tail of the queue
// to the position. Ensure the head and tail pointers
// are correct. Set the to busy and to null.
static void DoQueTask() {
if (NewTask > TOTALTASKS)
Status = 0;
else {
// Is there a tail task?
if (TailTask != 0)
task[TailTask].next = NewTask; // link tail to new
else
TaskHead = NewTask; // else new is head
TailTask = NewTask; // tail always new
task[NewTask].next = 0; // tail link always null
task[NewTask].delay = 0; // not really required
task[NewTask].status |= (BusyBit | ReadyBit);
task[NewTask].pntr = *Tpntr; // insert task pointer
Status = task[NewTask].status; // set status
}
NewTask = 0;
}
//**********************************************************
// Take the task pointer and insert it in the data
// structure at the correct point in the delay queue such
// that the delay for this queue position equals the sum of
// all earlier delays. Ensure the links to and from this
// queue position use the list position.
static void DoQueDelay() {
int Pntr0, Pntr1;
int OldIntVal, IntVal;
if (NewTask > TOTALTASKS)
Status = 0;
else {
IntVal = NewDelay; // initial delay
Pntr0 = *DelayHead; // start from head
Pntr1 = 0;
// While not at end of list and a still positive delay.
while (Pntr0 && IntVal > 0) {
OldIntVal = IntVal; // save old interval
IntVal -= task[Pntr0].delay; // compute next
// If interval is still positive
if (IntVal > 0) {
Pntr1 = Pntr0; // step down the queue
Pntr0 = task[Pntr1].next;
}
}
// We are either at the end of the queue ( == 0) or
// at the correct place in the queue to insert this task.
task[NewTask].next = Pntr0; // link this task in
task[Pntr1].next = NewTask;
if (Pntr0 == 0)
task[NewTask].delay = IntVal; // we are at the end
else {
if (Pntr0 == Pntr1)
task[Pntr1].next = 0;
task[NewTask].delay = OldIntVal; // fix up us
task[Pntr0].delay = -IntVal; // and the next entry
}
task[NewTask].status |= (BusyBit | DelayBit);
task[NewTask].pntr = *Tpntr; // insert pointer
Status = task[NewTask].status;
}
NewTask = 0;
NewDelay = 0;
}
//**********************************************************
// Set a possibly zero .
static void DoDelay(int delay) {
if (delay)
NewDelay = delay;
else
NewDelay = 0;
}
//**********************************************************
// Run the task at . When it returns clear the
// status and link. If it has re-scheduled itself, put it on
// the correct queue, otherwise clear the entry completely.
static void RunTask(void) {
(*task[RunningTask].pntr)(); // run the task
task[RunningTask].status &= ~(BusyBit | ReadyBit);
task[RunningTask].next = 0;
// If the task has re-scheduled itself
if (NewTask == RunningTask) {
Tpntr = task[NewTask].pntr; // set task pointer
// Put on correct list.
if (NewDelay)
DoQueDelay();
else
DoQueTask();
}
else
task[RunningTask].pntr = 0; // ensure task is idle
RunningTask = 0;
}
//**********************************************************
// Find either the first free entry in the data structure.
static void GetNewTask() {
void (*ptr)();
NewTask = 1;
while (NewTask <= TOTALTASKS) {
ptr = task[NewTask].pntr;
if (ptr == 0)
break;
NewTask++;
}
}