Electronics
Btrieve
Motorcycling
Software

Cooperative Multitasking Library

Short description

CMT is a lightweight cooperative multitasking 'kernel' that multitasks C functions. The library also provides several other useful tools:

  • Simple message-passing between tasks
  • Message queues
  • Semaphores
  • Timers
  • User hooks are provided for notification of task state changes.

Download Cooperative Multitasking Library

This includes full source code, documentation and compiled library. Due the nature of all such code, this is strictly Borland-specific (I'm using BC++ 3.1, 2.x versions should also work). If you want to use it with other compilers, you will probably have to change task management functions to save enough context for it to work.

Using the library

Library functions and data structures are described in "cmtlib.h", you will have to include it in your code. After that, you would typically create some tasks in your main function like that.


#include "cmtlib.h"

void task(void)
{
  while (1) {
    cmt_pause();
  }
}

void main(void) 
{
  cmt_fork(task,2048);
  while (1) {
    cmt_pause();
  }
}

What you just created is a program that runs two tasks endlessly, switching from one to another. Second parameter (2048) for cmt_fork() indicates a needed stack size for new task. The key to everything is cmt_pause() function that switches away from a caller, runs all other task and then returns to caller as if nothing happened. While the above example is perfectly valid code, it does not do all that much. Lets have a look at more interesting example:


#include "cmtlib.h"
#include <stdio.h>
#include <conio.h>

void task(void)
{
char *s;
  s=cmt_receive();
  while (1) {
    printf("%s\n",s);
    cmt_sleep(1);
  }
}

void main(void)
{
  cmt_send("one",cmt_fork(task,2048));
  cmt_send("two",cmt_fork(task,2048));
  cmt_send("three",cmt_fork(task,2048));
  while (!kbhit()) {
    cmt_pause();
  }
  getch();
}

So what is going on in here? We are creating three instances of a same task, and give each of them a different 'job' by sending a message. cmt_fork() returns a pointer to new task (error checking is not done for clarity) and cmt_send() sends a string as message to that task. At that point, the message is just stored into task data. On first call to cmt_pause() each instance gets control in round-robin fashion and as a first thing receives a message that was sent to it. after that they all go to endless loop, printing the received string and then calling cmt_sleep() - a sleeper function that continously calls cmt_pause() for given number of seconds. Meanwhile, the main program is busy watching the keyboard.

You can figure out the semaphores, timers and message queues from source code and documentation yourself, I'll just give one more example, this time from real-life code:

#include "cmtlib.h"
#include <dos.h>

static void interrupt far (*oldtick)(void);
static long watchdog,wdticks;

static void interrupt far newtick(void)
{
  (*oldtick)();
    watchdog++;
  if (watchdog>wdticks) {
    _asm cli
    _asm pushf
    _asm mov    ax,0ffffh
    _asm push   ax
    _asm xor    ax,ax
    _asm push   ax
    _asm iret
  }
}

void startwatchdog(int sectimeout)
{
  watchdog=0l;
  wdticks=(long)sectimeout*18l+(long)(sectimeout/5);
  if (!oldtick) {
    oldtick=getvect(0x1c);
    setvect(0x1c,newtick);
  }
}

void stopwatchdog(void)
{
  if (oldtick) {
    setvect(0x1c,oldtick);
    oldtick=NULL;
  }
}

void clearwatchdog(void)
{
  watchdog=0l;
}

/*--------------------------------*/
void idletask(void)
{
  while (1) {
    clearwatchdog();
    sleep(1);
  }
}

void main(void)
{
  startwatchdog(60);
  cmt_fork(idletask,512);
  while (!kbit()) {
    cmt_pause();
  }
  stopwatchdog();
  getch();
}

Of course, in real life you would probably have other tasks. This code implements a watchdog that can detect that your code is dead - provided the timer interrupt is still alive. startwatchdog() will attach new interrupt handler to user timer handler which counts ticks and reboots machine if a predefined amount is reached. idletask() clears the counter once in a second, never allowing the counter to overflow as long as the multitasker runs normally. Startwatchdog() is implemented so that in can be called more than once to set a new timeout value - I'm setting it to something like 10 minutes and sometimes longer if I need to execute external programs from my code. This simple watchdog works very well in practice, recovering automatically from most errors (including troublesome network problems, sharing violations and intermittent device I/O errors).

Hopefully this code proves useful for you all. I'll be glad to receive any donations from you if it does, but you have no obligation to send me anything. This code just wants to be free :).

Copyright © Madis Kaal 2000-