release 0.0.0.20 and thoughts

spc@gate.net spc@gate.net
Thu, 17 Aug 1995 01:52:07 -0400 (EDT)


WARNING!  This is quite long, and includes sample code.  You have been
warned.

On some network somewhere in cyberspace, Billy Tanksley transmitted:
> 
> > > >From my point of view, it is.  If you force virtual memory on everything, 
> > > many things will have trouble.  If you only provide virtual memory as a 
> > > service to things that need it, nothing will break.  (Simplified case.  
> > > Murphy's law may apply otherwise in some finite states.)
> 
> >   Care to name what virtual memory causes troubles with?  About the only
> > argument I can think of is that under certain systems, virtual memory
> > comsumes space for the tranlation tables (not all system have this trouble,
> > notably those where the virtual memory is handled externally to the CPU) and
> > are *slightly* slower than non-virtual memory systems (I'm excluding
> > swapping out to disk here in this case).
> 
> Virtual memory includes swapping to disk.  That's the meaning of 
> virtual.  If you mean memory protection...
> 
  Okay.  But still can you answer the question?  This time with BOTH memory
protection AND virtual memory.

> > > Okay.  I'll accept that.  Do you accept my point, that what we can 
> > > discover about this does have a certain amount of utility?
> 
> >   I'll accept that.
> 
> Then it is clear that there is some benifit in proving such things about 
> it, and thereby reducing the supervisory burden on the system.
> 
> Actually, the total amount of supervision is the same; the difference is 
> that by proving those things about the task the system can ignore those 
> aspects of the task during runtime.  You lose a constant amount of time 
> at startup, you gain a linear amout of time during running.
> 
  Okay, that I'll buy.

> > > > > As I understand it, the reverse is also true.  Coop multi does not 
> > > > > prevent an object from preempting its own decendants.
> 
> > > >   Explain how that is done.
> 
> > > Cooperative multitasking, simply defined, is allowing tasks to return 
> > > control on their own time rather than taking it away from them at any 
> > > time.
> 
> >   Right.
> 
> > > If computing time is distributed to objects through some sort of tree,
> > > with the processor being the trunk, you can easily define a 'branch' of
> > > that tree that takes back control from all its decendants at fixed
> > 	         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> > > intervals.  
> >   ^^^^^^^^^^
> 
> >   Thank you for defining preemptive multitasking.  So kind of you.
> 
> Sarcasm will get us nowhere.
> 
  Sorry, but I didn't notice the (very) subtle shift in what you were doing.

> My point!  You can implement preemptive multitasking for a subset of a 
> cooperative system.
> 
> I'm saying that cooperative multitasking is more flexible than preemptive 
> multitasking.  See my above statement for what coop can do, and notice 
> that on the other hand, preemptive cannot ever fail to preempt or it 
> completely fails.
> 
  That last bit about preemptive cannot ever fail seems redundant to me. 
But to impliment preemptive multitasking within a cooperative multitasking
system there has to be some facility to interrupt a currently running
execution unit, else you can't preempt it!  And if this isn't the case, then
you very well CAN'T do preemptive multitasking.  Well, you can, but it'll be
a horrible kludge.

  But I contend that preemptive multitask is more flexible than cooperative
because you CAN do cooperative multitasking in a preemptive multitasking
system.  And to prove it, I'll do it!

  The following is demonstration code that impliments a cooperative
multitasking system (must like the joyous people at TUNES want) withing a
preemptive multiask system, in this case, the Amiga.  Sorry, but I resorted
to low level C and Assembly to do this, and I was able to do this using only
one (1) Exec (kernel) hack to keep Exec in sync with the currently running
cooperative task (and with some rewriting, even this can be done away with). 
This done in 71 lines of C code and 112 lines of Assembly.  Any routines you
don't see code for are system routines.

  The code creates six cooperative tasks within the framework of the
preemptive system.  To make it simulate a complete cooperative system,
uncomment the calls to Forbid() and Permit().

WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING!

  Due to the implimentation of IO under AmgiaDOS, you cannot do IO with the
code as it stands right now.  Even under the full preemptive system, there
are restrictions as to what form of IO tasks can do.  This code is for
demonstrating that coooperative multitasking CAN BE DONE IN THE MANNER THAT
TUNES CURRENTLY DEFINES IT is possible.  Doing IO is left as an exercise for
the reader.

WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING!

  -spc (not bad for some two hours work 8-)

--------------[ C code ]----------------------------------------------

/* include any headers required */

struct List  tasks_run;
struct List  tasks_wait;

#define Yield()		(*defield)()
void (*defield)(void) = NullYield;

#define STACKSIZE	4096
#define INITSTACK	STACKSIZE-4

void TaskStart(void (*r)(void),char *name)
{
  struct Task   *pt;
  unsigned long *pl;
  
  pt = AllocMem(sizeof(struct Task),MEMF_PUBLIC | MEMF_CLEAR);
  pt->tc_SPLower      = AllocMem(STACKSIZE,MEMF_CLEAR);
  pt->tc_SPUpper      = STACKSIZE + pt->tc_SPLower;
  pt->tc_SPReg	      = INITSTACK + pt->tc_SPLower;
  pt->tc_Node.ln_Type = NT_TASK;
  pt->tc_Node.ln_Pri  = 0;	/* not used here currently */
  pt->tc_Node.ln_Name = name;
  
  TaskRun(pt,r);
  AddHead(&tasks_run,pt);
}

int timer_irq(void)
{
  static unsigned int  timeslice = 0;
  struct Task         *ptnext;
  
  if ((timeslice++ & 0x0001) == 0)
  {
    ptnext = RemHead(&tasks_run);
    if (ptnext == NULL) return(0);
    AddHead(&tasks_run,ptnext);
    AddTail(&tasks_run,FindTask(NULL));
    defyield = FullYield;
  }
  return(0);
}

void task(void)
{
  unsigned long l;
  
  for(l = 0 ; l < ULONG_MAX ; l++ , Yield())
  {
  }
}
  
int main(void)
{
  InitList(&tasks_run);
  InitList(&tasks_wait);
  AddIntServer(INTB_VERTB,timer_irq);
  /* Disable();		/* uncomment to disable multitasking system wide */
  TaskStart(task,"Coop task 1");
  TaskStart(task,"Coop task 2");
  TaskStart(task,"Coop task 3");
  TaskStart(task,"Coop task 4");
  TaskStart(task,"Coop task 5");
  TaskWait();		/* wait for 5 tasks to finish */
  /* Enable();		/* uncomment to enable multitasking system wide */
  RemIntServer(INTB_VERTB,timer_irq);
  return(0);
}

------------[ 68K Assembly Code ]--------------------------------

; include any headers required

;-------------------------------------------------------------------

		public	_NullYield
		public	_FullYield
		public	_TaskEnd
		public	_TaskRun

		public	_defyield
		public	_tasks_run
		public	_tasks_wait

;-------------------------------------------------------------------

;*******************************************************************
		section	_code,code

_NullYield	rts

_FullYield   	movem.l	a2-a6/d2-d7,-(a7)	;save state info
		move.l	ExecBase,a6
		jsr	_LVODisable(a6)		;disable IRQs
		move.l	#_NullYield,_defyield
		sub.l	a1,a1
		jsr	_LVOFindTask(a6)
		move.l	d0,a1
		move.l	a7,TC_SPREG(a1)		;save stack pointer
		move.l	#tasks_run,a0
		jsr	_LVOAddTail(a6)		;put back into run queue

taskkickoff	move.l	#tasks_run,a0		;get next task to run
		jsr	_LVORemHead(a6)		;should always return valid
		move.l	d0,a0			
		move.l	TC_SPREG(a0),a7		;restore stack pointer
		move.l	a0,ThisTask(a6)		;hack to keep Exec in sync
		jsr	_LVOEnable(a6)		;enable IRQs
		movem.l	(a7)+,a2-a6/d2-d7	;restore state info
		rts

_TaskEnd	move.l	ExecBase,a6
		jsr	_LVODisable(a6)		;disable IRQs
		sub.l	a1,a1
		jsr	_LVOFindTask(a6)
		move.l	d0,a2			;save this task pinter
		subq.l	#1,taskcount
		bne.s	taskend.10
		move.l	#tasks_wait,a0
		bra.s	taskend.20
taskend.10	move.l	#tasks_run,a0
taskend.20	jsr	_LVORemHead(a6)
		move.l	d0,a1
		move.l	TC_SPREG(a1),a0		;set it up so we return
		movem.l	a2-a6/d2-d7,-(a0)	;back here to finish up
		pea	taskend.resume(pc),-(a0)
		move.l	a0,TC_SPREG(a1)
		move.l	#tasks_run,a0		;put this task in the running
		jsr	_LVOAddHead(a6)		;queue and make sure we're
		move.l	#tasks_run,a0
		move.l	a2,a1
		jsr	_LVOAddHead(a6)
		bsr.w	_FullYield
taskend.resume	move.l	TC_SPLOWER(a2),a1	;free stack
		move.l	#4096,d0
		jsr	_LVOFreeMem(a6)
		move.l	a2,a1			;free task structure
		move.l	#TC_SIZE,d0
		jsr	_LVOFreeMem(a6)
		movem.l	(a7)+,a2-a6/d2-d7
		rts
		
_TaskRun	move.l	4(a7),a0		;get task pointer
		move.l	TC_SPREG(a0),a1
		move.l	8(a7),-(a1)		;push initial PC
		movem.l	a2-a6/d2-d7,-(a1)	;inherit registers
		addq.l	#1,taskcount
		rts				;that's all!

_TaskWait	movem.l	a2-a6/d2-d7,-(a7)
		move.l	ExecBase,a6
		jsr	_LVODisable(a6)
		sub.l	a1,a1
		jsr	_LVOFindTask(a6)
		move.l	d0,a2
		move.l	d0,a1
		move.l	#tasks_wait,a0
		jsr	_LVOAddHead(a6)
		pea	taskwait.resume(pc),-(a7)
		movem.l	a2-a6/d2-d7,-(a7)
		move.l	a7,TC_SPREG(a2)		;save stack pointer

		bra.w	taskkickoff		;get next thread to run

	;--------------------------------------------
	; welcome back!  IRQs are enabled at this point.  Also, the system
	; has already popped the second set of a2-a6/d2-d7 off the stack,
	; so do any clean up, pop the first set of registers off the stack,
	; and off we go!
	;--------------------------------------------

taskwait.resume	movem.l	(a7)+,a2-a6/d2-d7
		rts

;**********************************************************************
		section	_data,data

taskcount	dc.l	0

;----------------------------------------------------------------------

		end