_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 #include #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 #include #include #include #include #include #include #include #include #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 #include // 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(); }