[net.micro.amiga] MULTI-TASKING ON THE AMIGA

robp@amiga.UUCP (Robert A. Peck) (03/20/86)

MULTI-TASKING
MULTI-TASKING
MULTI-TASKING
MULTI-TASKING
MULTI-TASKING

One of the questions that is often asked about the Amiga is:

	HOW DO I USE THE MULTI-TASKING CAPABILITIES?

There are actually two different levels of multi-tasking that are
possible on the Amiga, and which of these that you use depends on
the nature of the subtask you wish to perform.   

o	If your code is largely self-contained, and does no I/O and 
	uses no disk-resident library code, you can probably spawn 
	a task, as is demonstrated here in this message.    

o	If your code needs to do I/O, (or uses AmigaDOS functions
	in ANY way), it will have to be spawned as a process rather
	than as a task.   

Process creation and deletion is covered in a separate message.


WHAT IS COVERED HERE?

This message contains a functioning example that shows (one method) of
spawning a task for the Amiga.  Both the main program and the task are
loaded in the same block of code.  This is a complete program, but neither
main() nor litteltask() do anything significant on their own.  main()
simply spawns the littletask, then waits for it to say it is done.  
littletask() looks for a startup message from main(), responds to it,
and deletes itself. 

main() sleeps until the littletask replies to its startup message, then 
frees the memory that it provided for the littletask, and finally exits.  

main() can go on to do something else instead of simply waiting for the 
reply.  Likewise, littletask() can do <whatever> before it replies and
finally removes itself.   It's up to you.

The example was tested both from CLI and workbench; compiled under Amiga C, 
(Lattice 3.03).  Both Astartup.obj and Lstartup.obj linking is shown.  



DISCLAIMER:

	This program is provided as a service to the programmer
	community to demonstrate one or more features of the Amiga
	personal computer.  These code samples may be freely used
	for commercial or noncommercial purposes.

	Commodore Electronics, Ltd ("Commodore") makes no
	warranties, either expressed or implied, with respect
	to the program described herein, its quality, performance,
	merchantability, or fitness for any particular purpose.
	This program is provided "as is" and the entire risk
	as to its quality and performance is with the user.
	Should the program prove defective following its
	purchase, the user (and not the creator of the program,
	Commodore, their distributors or their retailers)
	assumes the entire cost of all consequent damages.  In 
	no event will Commodore be liable for direct, indirect,
	incidental or consequential damages resulting from any
	defect in the program even if it has been advised of the 
	possibility of such damages.  Some laws do not allow
	the exclusion or limitation of implied warranties or
	liabilities for incidental or consequential damages,
	so the above limitation or exclusion may not apply.


; assign lib df1:lib
; (df1: is C development disk.)
; NOTE.... complile with the -v option in LC2 to disable stack checking
; code.... otherwise linker coughs with undefined items _cxovf and _base.
; (because I'm not using Lstartup.obj and lc.lib.)
;
; IF RUN FROM CLI:

FROM lib:Astartup.obj ram:tasking.o ram:inittask.o 
TO ram:tasking
LIBRARY lib:amiga.lib

; Acceptable alternative linking, with stack checking left intact,
; allows run from Workbench by clicking on a custom icon.

FROM lib:Lstartup.obj, ram:tasking.o ram:inittask.o
TO ram:tasking
LIBRARY lib:lc.lib+lib:amiga.lib

; If you use the second alternate linking, you'll be able to start
; from Workbench, and the debug printf output will go to the small
; window that Lattice startup automatically opens for stdin/stdout.
; Note however that you'll need to uncomment the "Delay" at the end
; of main to actually see the results.



/* tasking.c */

/* Sample tasking program - 

	* Creation of a parent and child task.

	* Main task allocates space for a task control block and a
	* message port, to be dedicated to the child task.

	* Main initializes both port and tcb, then adds the child task.
	
	* Child goes to sleep on creation, waiting for a message
	* to be sent to its message port.  

	* On sending that message, parent task goes to sleep waiting 
	* childs reply.  

	* When parent's message arrives on child's port, child 
	* task awakes, retrieves the message and replies.  Goes into
	* an endless sleep (designer's choice).

	* Parent awakens on receipt of message on its reply port,
	* deallocates child memory utilization, and exits.

*/

/* author: Rob Peck   3/14/86 */

/* system software version: V1.1 or higher  */

/* program dependency information:

	Link with Astartup.obj, InitTask.o, amiga.lib
	to become 
			tasking 

*/


#include "exec/types.h"
#include "exec/nodes.h"
#include "exec/lists.h"
#include "exec/memory.h"
#include "exec/interrupts.h"
#include "exec/ports.h"
#include "exec/libraries.h"
#include "exec/tasks.h"
#include "exec/execbase.h"

#include "exec/io.h"

#define PRIORITY 0
#define STACKSIZE 500

extern struct Message 	*GetMsg();
extern struct MsgPort 	*CreatePort();
extern struct MsgPort 	*FindPort();
extern APTR 		AllocMem();
extern struct Task 	*FindTask();
extern int 		InitTask();

struct MyExtendedTask {
	   struct Task 		met_Task;	/* a Task control block */
	   struct MsgPort 	met_MsgPort;	/* to a message port */
	   int 			met_Status;	/* a status value for info */
}; 

littletask()
{ 
	int signalbit;	/* signal bit value for error checking */

	/* pointer to little task's message port */

	struct MsgPort *mp;   

	/* pointer to little task's message */

	struct Message *msg;	

	/* pointer to a task; in this case, us */

	struct MyExtendedTask *met;
	
	met = (struct MyExtendedTask *)FindTask(0);	

	/* use this to mean everything is ok so far */

	met->met_Status = 0;

	/* Point to the message port */

	mp = &(met->met_MsgPort);

	/* Now we have to set up the message port so that we can
	 * go to sleep waiting for a message to arrive there.
	 *
	 * Tell the port which task gets signaled when this port
	 * receives a message.  Nothing happens yet since mp_Flags
	 * is set to PA_IGNORE by the master task.
	 */
	mp->mp_SigTask = (struct Task *)met;	

	/* Now get a signal bit number... this won't fail because
	 * this task is brand new and has plenty of signal bits
	 * to go around.  Error checking done anyway, but what to
	 * do if it fails is up to user in his own code.
	 */
	signalbit = AllocSignal(-1);	/* allocate ANY signal bit */
	if(signalbit != -1)
	{
		/* A valid signal bit has been allocated! */
		/* NOW, tell the port to signal us if a message is received */
		mp->mp_Flags = PA_SIGNAL;	
	}
	else
	{
		/* no signal bit was available */
		met->met_Status = -1;
		goto finish;
	}
	/* If there was an error, then the flags variable never gets set
	 * to other than PA_IGNORE.  As a result, this poor little task
	 * might have to go to sleep forever waiting for a signal that will 
	 * never happen.  Thus, a master task/process could send a message
	 * to this little task, then wait for a limited time, and finally
	 * if no response from the little task, it can check the taskstatus
	 * to see if the little task actually started.  If it did not,
	 * then the error code or current status can be in the status 
	 * variable.  Instead of forcing this action, we have chosen
	 * to terminate the task instead. (goto finish) 
	 */

	WaitPort(mp);	  /* wait for signal bit to be set */

	/* If there is a message already there, we never even sleep */

	msg = GetMsg(mp);	

	/* successfully responded to a message */

	met->met_Status = 1;	

	/* 
		DO SOME OTHER GOOD STUFF HERE.... WHATEVER
		THE TASK WAS SUPPOSED TO DO
	 */

    finish:

	Forbid();	/*  Disable task switching */
	ReplyMsg(msg);	/*  Send message back to starter */
	/*
 	 * One would expect to free this signal bit using FreeSignal
 	 * in about this position.  However, since the task is about
 	 * to be removed anyway, why bother.
 	 */
	RemTask(0);	/* Permit() happens automatically when remove self */	
	/* 
	 * Remove ourselves from task list; the next ready task 
	 * can begin to execute.  We can use this because of the 
	 * MemList stuff in InitTask().... any memory
	 * on the task's memlist is returned to the system 
	 * automatically when RemTask is performed.
	 */

	/* NOTICE that littletask MUST end in some kind of a 
	 * Wait() or an endless loop, or somehow cause itself
	 * to be removed as is shown here.  If it falls off the end
	 * of the world, there is no place to fall into, and
	 * nobody to return to.  It must hold firm while the
	 * master task later deletes it.
	 */
}		/* end of little task */

/* ********************************************************************* */



/* ******************************NOTE *****************************

	This example does NOT have littletask try to printf-anything
	because littletask is only a task, not a process.  When you
	RUN something, (or simply start something from a CLI), that
	program gets attached to a "process", which is a superset
	of a task.  Those functions described in the ROM Kernel
	manual can be run by tasks.  Those functions described in
	the AmigaDOS Developers' Manual or the Lattice C manual
	must be called from main() or any of its own subroutines.
	Separate items, fired up as tasks rather than processes,
	must use only task-able functions.  (Delay(xx) is a DOS
	function, so littletask can't do that one either).

	(printf implies an output to an AmigaDOS controlled CON: or
	 RAW: window, is therefore an AmigaDOS interactive command
	 and requires a process call it rather than a task).

	A user of tasks must be especially careful about controlling
	the things that are requested by a task, primarily things
	that are entirely self-contained code are probably the best
	to use.   Anything that may utilize the DOS should be avoided.
	This includes opening a library, a device or a font, since
	each of these causes a disk access (to load nonresident code
	or devices).   You may discover work-arounds, but the general
	approach is that if your code has to do something that uses
	AmigaDOS, then you should probably spawn a PROCESS rather than
	a task.

	A separate example shows how to spawn a process rather than
	a task.  

	This tasking example works for resident code,
	where both the main() and its tasks are loaded at once.
	The process example actually loads the other program as
	a separate item and unloads its code when it finishes.

******************************************************************** */

main()
{
	struct Message mymessage;	/* an actual message data structure */
	struct MsgPort *mainmp;		/* pointer to main's reply port */
	struct MyExtendedTask *met; 	/* pointer to an extended task
					 * control block */
	struct MsgPort *mp;		/* pntr to littletask's message port */
	int result;			/* nonzero if InitTask works ok */

	printf("\n Started main()\n");

	mainmp = CreatePort(0,0);	

	/* Using for reply only, so don't we need to name it... 
	 * address for the reply is contained in the message itself. */

	if(mainmp == 0) exit(20);	/* error during createport */

	/* Set up the message data structure so that we can use PutMsg
	 * to transmit this to the little task we are creating.
	 */

	mymessage.mn_Node.ln_Type = NT_MESSAGE;
	mymessage.mn_Length  	  = sizeof(struct Message);
	mymessage.mn_ReplyPort    = mainmp;

	/* Now allocate space for an extended task control block and
	 * initialize it */

	met = (struct MyExtendedTask *)AllocMem(sizeof
			(struct MyExtendedTask), MEMF_PUBLIC | MEMF_CLEAR );

	if(met == 0) 
	{
		/* Error during AllocMem */
		DeletePort(mainmp);
		exit(40);	
	}

	/* Now initialize the task control block part of the extended task */

	result = InitTask( (struct Task *)met, "littletask", 
				PRIORITY, STACKSIZE );
	
	if (result == 0)
	{
		/* error during InitTask... no mem for stack */
		DeletePort(mainmp);
		exit(45);
	}
	/* Get the address of the message port */

	mp = &(met->met_MsgPort);

	/* initialize the message port for the little task. */

	mp->mp_Node.ln_Type = NT_MSGPORT;	/* is a message port */
	mp->mp_Flags = PA_IGNORE;		/* when message arrives, 
						 * don't try to signal */
	NewList(&(mp->mp_MsgList));		/* initialize message list */

	/* Now that the message port is set up, it is legitimate to send a
	 * message to it.  We can send a message before or after adding 
	 * the task 
	 */

	PutMsg(mp,&mymessage);	

	AddTask( met, littletask, 0 );

	printf("\n Created and added the little task");	

	WaitPort(mainmp);	/* wait for its reply */

	/* This example assumes that everything will go ok.  However
	 * if there is an error, littletask will wait forever, and so
	 * will main (since the message will never get back to the
	 * reply port.  The alternative is to set up a timer, and
	 * do a:
	 *
  	 * wakeupmask = Wait( TIMER_SIGNAL_BIT | REPLYPORT_SIGNAL_BIT );
	 *
	 * then if no response after a reasonable time, examine the
	 * met_Status to find out what went wrong with poor littletask
	 * and do something about it.
	 */

	GetMsg(mainmp);	/* remove the message */
	printf("\n main: Little task received my message\n");

cleanup:
	if(met) {
	  /* remove the littletask before we exit */
	  FreeMem(met, sizeof(struct MyExtendedTask));	
	}

	printf("\n main: Freed memory that littletask used");

	if(mainmp) {
	  /* and our message reply port */
	  DeletePort(mainmp);	
	}

	/* Delay(250);		* Wait 5 seconds before exiting so
				* that user can read the messages
				* that we output into the Lattice
				* Workbench window
	*/

}	/* end of main */




/* ************************************************************** */
/* InitTask.c - 

	1.  Use memory that has been allocated by somebody else, and
	    let them deallocate it later, as well. 

	2.  Initialize as much of the task control block as is appropriate
	    (same amount of init as CreateTask does).

************************************************************************ */

/* Author:  Rob Peck  3/14/86  */

/* Original code by Carl Sassenrath and Neil Katin; modified to allow
 * a higher degree of initialization to be done by the caller before
 * actually firing off the task.
 */

#include "exec/types.h"
#include "exec/nodes.h"
#include "exec/lists.h"
#include "exec/memory.h"
#include "exec/interrupts.h"
#include "exec/ports.h"
#include "exec/libraries.h"
#include "exec/tasks.h"
#include "exec/execbase.h"


/*
 *  Initialize a task with given name, priority, and stack size.
 *  It will use the default exception and trap handlers for now.
 */

/* The template for the mementries.  Unfortunately, this is hard to
 * do from C: mementries have unions, and they cannot be statically
 * initialized...
 *
 * In the interest of simplicity I recreate the mem entry structures
 * here with appropriate sizes.  We will copy this to a local
 * variable and set the stack size to what the user specified,
 * then attempt to actually allocate the memory.
 */

#define ME_STACK	0
#define NUMENTRIES	1	

struct FakeMemEntry {
    ULONG fme_Reqs;
    ULONG fme_Length;
};

struct FakeMemList {
    struct Node fml_Node;
    UWORD	fml_NumEntries;
    struct FakeMemEntry	fml_ME[NUMENTRIES];
} TaskMemTemplate = {
    { 0 },						/* Node */
    NUMENTRIES,						/* num entries */
    {							/* actual entries: */
	{ MEMF_CLEAR,	0 }				/* stack */
    }
};

int
InitTask( task, name, pri, stackSize )
    struct Task *task;
    char *name;
    UBYTE pri;
    ULONG stackSize;
{
    struct Task *newTask;
    struct FakeMemList fakememlist;
    struct MemList *ml;

    /* round the stack up to longwords... */
    stackSize = (stackSize +3) & ~3;

    /*
     * This will allocate one chunk of memory:
     * a stack of PRIVATE
     */
    fakememlist = TaskMemTemplate;

    fakememlist.fml_ME[ME_STACK].fme_Length = stackSize;

    ml = (struct MemList *) AllocEntry( &fakememlist );

    if( ! ml ) {
	return( 0 );
    }

    /* Set the stack accounting stuff */
    newTask = task;

    newTask->tc_SPLower = ml->ml_ME[ME_STACK].me_Addr;
    newTask->tc_SPUpper = (APTR)((ULONG)(newTask->tc_SPLower) + stackSize);
    newTask->tc_SPReg = newTask->tc_SPUpper;

    /* Misc task data structures */
    newTask->tc_Node.ln_Type = NT_TASK;
    newTask->tc_Node.ln_Pri = pri;
    newTask->tc_Node.ln_Name = name;

    /* Add it to the tasks memory list */
    NewList( &newTask->tc_MemEntry );
    AddHead( &newTask->tc_MemEntry, ml );

    return( 1 );
}