home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Power-Programmierung
/
CD1.mdf
/
magazine
/
drdobbs
/
1991
/
04
/
cpptask.asc
< prev
next >
Wrap
Text File
|
1991-03-15
|
16KB
|
450 lines
_COOPERATIVE MULTITASKING IN C++_
by Marc Tarpenning
[LISTING ONE]
// File: TASK.H
// Task object header -- Each task object contains its own stack, links to next
// and previous task objects in the round robin, the saved stack pointer, and
// debug information.
class Task {
int *area; // base stack location
int id; // task id number for debuging purposes
Task *next; // next task in chain
Task *prev; // prev task in chain
int *saved; // saved stack location during pause
int *top; // top stack location
public:
void Activate(void (*)() ); // starting function
int GetId() // return id number
{ return id; }
Task *GetNext() // return next pointer
{ return next; }
int *GetSP() // return saved sp
{ return saved; }
void SetNext(Task *t) // sets next pointer
{ next = t; }
void SetPrev(Task *t) // sets prev pointer
{ prev = t; }
void Show(); // display data for debugging
void Switch(); // context switch to next task
Task(int); // constructor
~Task(); // destructor
};
Task *fork(void (*)() ); // forks tasks
Task *InitTasking(); // Initializes all task stuff
void pause(); // switches to next task
extern int totalTasks; // debug counter of total tasks
[LISTING TWO]
// File: TASK.CPP
// Cooperative Multi-tasking in C++ --
// Marc Tarpenning, P.O. 254801, Sacramento, CA 95865
// Cis: 71435,1753 uucp: met@sactoh0.SAC.CA.US
#include <conio.h>
#include <stdlib.h>
#include "task.h"
// Protypes
void terminate(); // terminate task on completion
// Defines
#define STACKSIZE 0x200 // Stack size of each task
// Global variables
Task *CurrentTask = 0; // current task executing
Task *SystemTask; // main task using system stack
int totalTasks = 0; // debug counter of tasks
// Task.Activate - sets up the task object's stack so the when the task is
// switched to, the passed function is performed.
void Task::Activate(void (*f)() )
{
saved = top; // reset stack
*(--saved) = (int) &terminate; // kill task function on exit
*(--saved) = (int) f; // place function for return address
*(--saved) = _BP; // save all registers for switch
*(--saved) = _SI; // save SI
*(--saved) = _DI; // save DI
}
// Task.Show - Debug information is displayed
void Task::Show()
{
cprintf("Task: %4i area: %04X\n\r",id,area);
cprintf(" top: %04X saved: %04X\n\r",top,saved);
cprintf("prev: %04X next: %04X",prev,next);
}
// Task.Switch - switch context to next task object in the round robin.
// It saves the current stack pointer, gets the stack pointer of the
// next task (after making it current), and returns.
void Task::Switch()
{
_SI = _DI; // force compiler to save SI and DI
saved = (int *) _SP; // store stack pointer
CurrentTask = next; // set current task to next task
_SP = (int) CurrentTask->saved; // restore new task's stack pointer
}
// Task.Task - Initializes the new task object. Threads the object into
// the linked round robin of other tasks. If size is 0, then does not
// allocate any stack space and uses the system stack instead (system).
Task::Task(int size)
{
static int newid = 0; // unique identifier for each task
id = newid++; // set ID and inc
totalTasks++; // inc debug counter of total tasks
if (size) { // Want to create operator task?
if ((area = (int *) malloc(size * 2)) == 0) // No, so allocate
{
cprintf("Not enough memory to create task %i\n", id);
exit(1);
}
top = area + size; // set absolute top of stack
saved = top; // default saved stack to top
next = CurrentTask->GetNext(); // link task in chain
prev = CurrentTask;
prev->SetNext(this); // set current task to point to me
next->SetPrev(this); // set next task to point to me
} else { // operator task, so don't allocate stack
top = (int *) _SP; // instead, co-opt system stack
saved = top;
next = this; // since first task, make point
prev = this; // to myself
}
}
// Task destructor - return all allocate memory to the system.
Task::~Task()
{
totalTasks--; // dec debug counter of total tasks
prev->SetNext(next); // unthread this task from the round robin.
next->SetPrev(prev);
CurrentTask = next; // Set new current task
if (area) // don't free if no stack allocated (system)
free(area); // free object's stack
}
// fork - creates a new task to execute the passed function. When
// the function has completed, the task is automatically destroyed.
// fork returns a pointer to the new task object or NULL if out of memory.
Task *fork(void (*f)() )
{
Task *newtask; // pointer to new task object
// In small memory models, malloc uses the stack pointer to
// determine if there is any free memory on the heap.
// To allow forking from sub-tasks, we "borrow" the system stack
// for the malloc operation.
int temp = _SP; // save current stack pointer
_SP = (int) SystemTask->GetSP() - 20; // borrow system stack
// create new task object
if ( (newtask = (Task *) new Task (STACKSIZE)) )
newtask->Activate(f); // Setup new stack to execute function
_SP = temp; // restore original stack
return newtask; // return a pointer to the new object
}
// InitTasking - Initializes anything required before multitasking can
// begin. This function must be called before any other tasks are
// forked. It creates the "system" task by coopting the system
// stack into a task object (task # 0). It also sets CurrentTask
// to point to the new operator task.
Task *InitTasking()
{
CurrentTask = (Task *) new Task(0); // create system task
SystemTask = CurrentTask; // set system task pointer
return SystemTask; // return with pointer to system task
}
// pause - non-object interface to switch context.
void pause()
{
CurrentTask->Switch(); // context switch out of current task
}
// terminate - kills the current task when the fork is over. This is not
// a method, but its address is setup on the initial stack so if the
// task's function ever returns, terminate will be the next execution addr.
void terminate()
{
_DI = _SI; // force compiler to save DI and SI
delete CurrentTask; // kill the current task
_SP = (int) CurrentTask->GetSP(); // set to next task's stack and
// return into the new task
}
[LISTING THREE]
// File: DEMO.CPP
// Demo program for cooperative multitasking objects
// Marc Tarpenning, P.O. 254801, Sacramento, CA 95865
// Cis: 71435,1753 uucp: met@sactoh0.SAC.CA.US
// General includes for prototype headers
#include <iostream.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <conio.h>
#include <string.h>
#include <bios.h>
#include <ctype.h>
#include <dos.h>
#include "task.h"
#include "twindow.h"
// Prototypes for simple demo functions
void endlessCount();
void fiveseconds();
void funwindow();
void msdelay(int);
int newgetch();
void periodic();
void quicky();
void status();
void wallclock();
main()
{
/* Init multi-tasker. Creates system parent task */
InitTasking(); // init task, coopt system stack
/* ---- Init screen ----- */
textattr(WHITE); // set "normal" white on black
clrscr(); // clear screen
_setcursortype(_NOCURSOR); // kill cursor
/* ----- start up some tasks ----- */
fork(&endlessCount); // spawn endless counter task
fork(&wallclock); // spawn clock task
fork(&periodic); // spawn periodic launcher task
fork(&funwindow); // spawn strange window
fork(&status); // spawn total number of tasks
/* ----- create main window for user commands ---- */
TextWindow myWindow(1,20,80,25,(LIGHTGRAY<<4)+BLUE);
gotoxy(20,1);
cputs("*** Cooperative Multitasking Demo ***\r\n");
cputs(" Each one of the windows is a seperate task object ");
cputs("executing a C++\r\n");
cputs(" function. All are running 'concurrently' using the ");
cputs("pause() context\r\n");
cputs(" switch routine.");
gotoxy(2,6);
cputs("Commands: [M]ake new task, [Q]uit");
/* ----- wait for input & process key strokes ------ */
for (;;)
switch ( toupper( newgetch() ) ) {
case 'Q': // quit - clean up screen and leave
window(1,1,80,24);
textattr(WHITE);
clrscr();
_setcursortype(_NORMALCURSOR);
return(0);
case 'M': // make - fork a new quick task
fork(&quicky);
break;
default: // illegal character
sound(500);
msdelay(160);
nosound();
break;
}
}
// endlessCount - opens a window and counts up forever.
void endlessCount()
{
TextWindow myWindow( 40,7,64,8,(CYAN<<4)+RED );
cprintf(" This task never ends!");
long count = 0;
for(;;) { // just keep counting, but
myWindow.Activate(); // don't forget to pause
gotoxy(1,2);
cprintf(" Current count: %li",count++);
pause(); // let other tasks run
}
}
// fiveseconds - opens a window, counts for 5 seconds, and returns
void fiveseconds()
{
TextWindow myWindow( 5,5,35,7,(GREEN<<4)+RED ); // make text window
cprintf(" This is a temporary task");
gotoxy(2,3);
cprintf("which only lasts five seconds");
time_t t; // get current time
t = time(NULL);
int i = 10000; // count down from 10000
while (difftime(time(NULL),t) < 5) { // keep counting down until
myWindow.Activate(); // difftime is five seconds
gotoxy(13,2); // or more.
cprintf("%5i",i--);
pause(); // let other tasks run
}
}
// funwindow - displays moving character in window
void funwindow()
{
TextWindow myWindow(65,10,78,10, (BROWN<<4) + YELLOW);
for(int i=0;;i = ++i % 20) { // forever move i from 0 to 19
myWindow.Activate();
gotoxy( abs( ((i/10) * -20) + i) + 1 ,1); // calc cursor
cputs(" * "); // so range is 1..10 then 10..1
msdelay(100); // delay ~ 100 ms
}
}
// msdelay - delays the number of milliseconds with ~ 55ms resolution
void msdelay(int delay)
{
long ticksplus = biostime(0,0L) + delay / 55;
while (biostime(0,0L) < ticksplus) // wait until time has passed
pause(); // let other tasks run
}
// newgetch - does same as getch, except calls pause while waiting
// for a keyboard hit.
int newgetch()
{
while (!kbhit())
pause();
return getch();
}
// periodic - occasionally launchs another task
void periodic()
{
TextWindow myWindow(1,10,41,11,(LIGHTGRAY<<4) + MAGENTA);
cputs(" Every ten seconds launch temporary task");
for (;;) {
for (int i=0; i < 10; i++) { // loop ten times before forking
myWindow.Activate();
gotoxy(20,2);
cprintf("%i",i); // display current count
msdelay(1000); // delay ~ one second
}
fork(&fiveseconds); // spawn new task which dies in 5 sec
}
}
// quicky - opens window, hangs around for a few seconds, and leaves
void quicky()
{
static int xpos = 0; // x position of new task window
static int ypos = 0; // base y of new task window
TextWindow myWindow( xpos+1,ypos+12,xpos+16,ypos+12,(GREEN<<4)+BROWN);
xpos = (xpos+3) % 64; // inc x position of "step" windows
ypos = ++ypos % 7; // inc y but keep within 7 lines
for (int i=0; i < 10; i++) { // count down for ten seconds
myWindow.Activate();
cprintf(" Dead in ten: %i",i);
msdelay(1000); // delay ~ one second
}
}
// status - displays the number of tasks running
void status()
{
TextWindow myWindow(1,1,18,1, (CYAN<<4) + MAGENTA);
for (;;) {
myWindow.Activate();
cprintf(" Total tasks: %2i", totalTasks ); // display total
msdelay(200); // delay ~ 200 ms
}
}
// wallclock - continuously displays the current time
void wallclock()
{
TextWindow myWindow( 55,1,80,1, (LIGHTGRAY << 4) + BLUE);
time_t t; // will hold the current time
char buf[40]; // temp buffer so can kill the \n
for (;;) { // always keep updating the time
myWindow.Activate();
t = time(NULL); // get the current time string address
strcpy(buf,ctime(&t)); // copy the string into temp
buf[24]='\0'; // kill the \n so window won't scroll
cprintf(" %s",buf); // display it
msdelay(1000); // wait for ~ one second
}
}
[LISTING FIVE]
// File: TWINDOW.H -- Demo text window objects -- These window objects create
// a primitive text window for demo program.
// Assume Borland C++ libarary functions
class TextWindow {
int attrib; // text mode attribute
int left,top; // starting x and y position
int right,bottom; // ending x and y position
public:
void Activate(); // make active
TextWindow(int,int,int,int,int); // constructor
~TextWindow(); // destructor
};
[LISTING FIVE]
// File: TWINDOW.CPP -- Demo text window objects
// Marc Tarpenning, P.O. 254801, Sacramento, CA 95865
// Cis: 71435,1753 uucp: met@sactoh0.SAC.CA.US
#include "twindow.h"
#include <stdio.h>
#include <conio.h>
// Window activation - makes this window the active window.
void TextWindow::Activate()
{
window(left,top,right,bottom); // Use C++ library
textattr(attrib); // to make window active
}
// Window constructor - store coordinates and clear window
TextWindow::TextWindow(int l,int t,int r,int b,int a)
{
left = l; // set up all instance variables
top = t;
right = r;
bottom = b;
attrib = a;
Activate(); // activate window
clrscr(); // clear window
}
// Window destructor - clears window with black background
TextWindow::~TextWindow()
{
Activate();
textattr( (BLACK << 4) + WHITE);
clrscr();
}