Tics Realtime -----


 
Home Services Products Tutorials Contact Us
- - - - -

Understanding Task Instances

This article discusses task instantiation and task instance data.

Task Instances

Consider a system with 3 analog channels that must be managed and controlled. Specifically, the following functions must be performed on each channel.

  1. Set gain at initialization time.

  2. Sample (read) the channel every "n" milliseconds.

  3. Display channel reading.

  4. Report an error if the reading is out of range.

As indicated above, the actions to be performed on each of the 3 channels is the same; however, the data associated with each channel is unique. The data required for each channel is listed below.

  1. Channel address.

  2. Channel gain.

  3. Channel sample rate in milliseconds.

  4. Min and max values used to determine when reading is out of range.

Although a separate task could be written for each channel task, it is preferable to implement a generic table driven task as shown below..

typedef struct structAnalogData {
	int gain;
	long pauseInMs;
	int reading
	int channelNum;
	int min, max;
} typeAnalogData;

void analogTask(void)
{
	typeAnalogData * d;

	/* At this point, the variable "d" is set to point
	to the instance data for the channel corresponding to
	this task instance. How this is done will be discussed
	below. For this code fragment, assume that "d" points
	to a structure of type "typeAnalogData" as defined 
	above. */

	setGain(d->gain);

	while (TRUE) {
		pause(d->pauseInMs);
		d->reading = readChannel(d->channelNum);
		if (d->reading < d->min || d->reading > d->max) {
			analogError(d);
		}
		display(d->reading);
	}
}
This code is generic in the sense that the logic is the same regardless of the channel. The only thing that can vary between channels is the value of the instance data pointed to by d.

What Task Instance Creation Means

Creation is the wrong word, because, as shown above, the task analogTask already exists. Task creation really means the creation of a task control block (tcb). The task control block is a C structure that contains information about the task. For our purposes in this article, the tcb has the structure shown below, which includes the task priority, a general purpose pointer, a task number that identifies the tcb, and a pointer to the C function where task execution is to begin, which in this case is the function analogTask.
typedef struct structTcb {
	int pri;		/* Task priority. */
	void * ptr;		/* General use. */
	int taskNum;		/* task number. */
	typeTask taskPtr;	/* Pointer to task function. */
	/* More data  not shown... */
} typeTcb;
For the purposes of this article, the function makeTask allocates a block of memory for the tcb, and sets the tcb as follows.
typeTcb * makeTask(typeTask * cFunction, int taskNum)
{
	typeTcb * tcb;

	tcb = getNewTcb();
	tcb->pri = DEFAULT_PRIORITY;
	tcb->ptr = NULL;
	tcb->taskNum = taskNum;
	tcb->taskPtr = cFunction;
	reutrn(tcb);
}

Starting Task Instances and Assigning Instance Data

The function startTask allocates a stack for the task, and adds the task to the ready queue. The task will then run according to its priority when multi-tasking begins, which happens when main is suspended (see code below). Task instance data may be assigned in 3 different ways, (described below), the distinction being largely a matter of choice.

Method 1 - Using the General Purpose tcb Pointer to Point to Instance Data

The general purpose tcb pointer can be used to point to instances data as shown in the code fragment from main shown below.
	typeAnalogData * d;
	typeTcb * tcb;

	tcb = makeTask(analogTask, 0);
	tcb->ptr = malloc(sizeof(typeAnalogData));
	d = (typeAnalog *) tcb->ptr;
	d->gain = 60;
	d->pauseInMs = 100L;
	d->reading = 0;
	d->channelNum = 30;
	d->min = 10;
	d->max = 90;

	startTask(tcb);	/* Schedule the task analogTask to run. */

	suspend();	/* Suspend the main thread and begin
			multi-tasking. */
Having set up the instance data, the task accesses the instance data as shown in the following code fragment.
	typeAnalogData * d;

	d = (typeAnalogData *) ActiveTcb->ptr;
The above is key to understanding task instances. The global variable ActiveTcb points to the tcb of the active task. ActiveTcb will be different for each instance of the task. This is why, even though all instances of the same task share the same code, their instance data is different because ActiveTcb points to a different tcb for each instance. The full code for analogTask using this instance data technique is shown below.
void analogTask(void)
{
	typeAnalogData * d;

	d = (typeAnalogData *) ActiveTcb->ptr;

	setGain(d->gain);

	while (TRUE) {
		pause(d->pauseInMs);
		d->reading = readChannel(d->channelNum);
		if (d->reading < d->min || d->reading > d->max) {
			analogError(d);
		}
		display(d->reading);
	}
}

Method 2 - Using the Task Number to Reference Instance Data

The task number can be used to reference index instance data as shown below.
	typeAnalogData AnalogData[3] = {
	{60, 100L, 0, 30, 10, 90},
	{50, 200L, 0, 40, 15, 85},
	{40, 300L, 0, 20, 20, 95}
	};

	typeAnalogData * d;

	d = &AnalogData[ActiveTcb->taskNum];
This technique uses a global array of instance data values. The task number is used as an index into the array. The task number is assigned by the programmer when makeTask is called (the task number is the second argument to makeTask). So, for the above to work properly, the 3 tasks must be assigned task numbers 0, 1, and 2 as shown below.
	startTask(makeTask(analogTask, 0)); /* Instance 0 */
	startTask(makeTask(analogTask, 1)); /* Instance 1 */
	startTask(makeTask(analogTask, 2)); /* Instance 2 */

Method 3 - Using the Local Stack to Store Instance Data

Because each task has its own private stack, the instance data can be stored on the stack as local data as shown below. This method is tedious and is presented here as an example; we recommend using Method 1 or Method 2, however, you may have to use a method like this, depending on the features of the kernel/OS that you use.
void analogTask(void)
{
	int gain, reading, channelNum, min, max;
	long pauseInMs;
	typeMsg * msg;

	msg = waitMsg(BEGIN);

	gain = msg->ia[0];
	channelNum = msg->ia[1];
	min = msg->ia[2];
	max = msg->ia[3];
	pauseInMs = msg->la[0];

	freeMsg(msg);

	setGain(gain);

	while (TRUE) {
		pause(pauseInMs);
		reading = readChannel(channelNum);
		if (reading < min || reading > max) {
			handleAnalogError();
		}
		display(reading);
	}
}
This technique waits for a message (typically issued from main after task has been started) that holds the instance data. Once the message has been received, the message data is copied into the local variables. Note that this technique does not require the task to access ActiveTcb to identify which instance it is, since the message was sent to a particular task instance, as shown in code fragment from main shown below.
#define BEGIN 1000

	int i;
	int channelNum[] = {30, 40, 20};
	int min[] = {10, 15, 20};.
	int max[] = {90, 85, 95};
	int gain[] = {60, 50, 40};
	long pauseInMs[] = {100L, 200L, 300L};
	typeTcb * tcb;

	for (i = 0; i < 3; i++) {
		tcb = startTask(makeTask(analogTask, i));
		msg = makeMsg(tcb, BEGIN);
		msg->ia[0] = gain[i];
		msg->ia[1] = channelNum[i];
		msg->ia[2] = min[i];
		msg->ia[3] = max[i];
		msg->la[0] = pauseInMs[i];
		sendMsg(msg);
	}
After the tcb for the task instance is created, a message for that instance is created and sent to it, i.e., sent to the tcb that represents the instance, and therefore, as stated above, the task does not need to reference ActiveTcb to find out which instance is active. Note also that we chose not to designate reading as instance data, as it really is not necessary. It was designated as instance data in the previous examples with the thought in mind of making available the latest reading.


We welcome comments. Let us know what subjects you would like written up. Send comments to Mike@TicsRealtime.com

Copyright © 2000, Tics Realtime