dillon@POSTGRES.BERKELEY.EDU (Matt Dillon) (12/16/88)
Which register or registers are used as the link register by Lattice C? I need to know so I can support both Aztec and Lattice in the following: I just thought up an as-yet untried method of programming on the amiga. Namely, very light weight synchronous processes. The idea works like this: main() { task1(); /* start the first lwp */ task2(); /* start a second lwp */ task1(); /* start a third lwp using the same code as the first */ run_lwp(); /* run lwp's until hell freezes over */ } task1() { startlwp(n); <initialization goes here> if (initlwp()) return(init_error_code); while (notdone) { .... waitlwp(); } endlwp(); } task2() { startlwp(n); <initialization goes here> if (initlwp()) return(init_error_code) while (notdone) { .... waitlwp(); } endlwp(); } Now, who can figure out what I am talking about here? The idea is for the initial call from main() to *create* a light weight process context for the subroutine and to then allow main() to continue and call other lwp's to create them. Thus, a direct call to task1() or task2() must return almost immediately so other calls can be made. This also means that you can create multiple lwp's using the same code (task?()) simply by calling it. startlwp() allocates a copy of the stack accessable to the subroutine. Specifically, M+N bytes are allocated where M is the number of bytes already being used by the subroutine (calculated via the current sp and the link register), and N is an additional amount that you will need for calls you make inside. startlwp() copies the subroutines stack data into the new stack, creates a light weight process descriptor and adds it to a global lwp list of some sort. The new stack pointer now becomes the stack's sp. You then have your initialization code. After this code, you must have: if (initlwp()) return(error_code). initlwp() returns TRUE when it is first called from a newly created descriptor and thus task?() will return back to main(). initlwp() also saves the state in the newly created descriptor so when the light weight process system begins to run, the context will come back to initlwp() (which will then return FALSE so you drop into your lwp loop). After that, initlwp() is never called again. The new task?() is now fully light weight. main() goes ahead and starts up a couple more of these babies, and then finally calls run_lwp(), which automatically cycles through those lwp's which are ready to go. This cycling is completely synchronous in that a lwp does not get interrupted, and can only loose cpu when it does a waitlwp() call. When the lwp is ready to die, it calls endlwp() (which returns the calling context and creates a stack frame that looks like the original call that started it all), and you then simply return. p.s.: run_lwp() does not call the entry point of the lwp() .. it always restores a context and then jumps to it. Since waitlwp() must be called to cause a context switch, only A2-A7/D2-D7/PC need be saved. A context switch is thus essentially: move.l _CurrentLWP,A0 ;descriptor or current lwp move.l ln_Next(A0),A1 ;next lwp to run tst.l (A1) ;end of list beq somewhereelse .1 movem.l D2-D7/A2-A7,wp_Context(A0) ;save current context move.l (sp)+,wp_PC(A0) ;save pc at call move.l A1,_CurrentLWP ;set next lwp movem.l wp_Context(A1),D2-D7/A2-A7 ;restore context move.l wp_PC(A1),-(sp) ;get pc for next lwp rts somewhereelse: ; circular list, get head. move.l -4(A1),A1 cmp.l A0,A1 ; only one lwp ready to run? bne .1 rts ; yes (actually, we would probably want to ; do a real _Wait() here). Not bad, eh? So, why would you want to do this when the amiga has multitasking? Because this allows you to modularize your code without the overhead of a task for each module! The context switch is much faster than with real tasks. But the real reason is to get out of the main-loop driven standard of programming (mac users are fond of quoting the fact that multitasking does not avoid the need for a main-event-wait-loop). Here, one would be able to segment a program into a lot of little programs each waiting for a different kind of event (or handling a different part of the data stream). Not only can you break up a program into a set of synchronously running lwps, but you can create real task contexts around pretty much arbitrary groups of lwps without having to modify the code associated with each module (if written right). So, I need ideas on the waiting part (that waits for some even and does the context switching). We need something that nicely complements Wait(), but in the lwp sense. Think about various types of events and things that one might want to wait for and then figure out an efficient way to handle N lwps each waiting for one type of event such that one does not need to poll. -Matt
rminnich@super.ORG (Ronald G Minnich) (12/17/88)
In article <8812151937.AA17954@postgres.Berkeley.EDU> dillon@POSTGRES.BERKELEY.EDU (Matt Dillon) writes: > Some interesting stuff on lwp functions he is proposing. Two thoughts: 1) Have you looked at the way either Sequent's microtasking library or the Spoc multiprocessor did it? Seems to me you want to create a set of context's for a set of lwps, then fire up the lwp's using those pre-created segments. Bot Spoc and the Sequent system do this well. > But the real reason is to get out of the main-loop driven standard >of programming (mac users are fond of quoting the fact that multitasking >does not avoid the need for a main-event-wait-loop). Here, one would >be able to segment a program into a lot of little programs each waiting for >a different kind of event (or handling a different part of the data stream). 2) But doesn't there have to be a loop somewhere? I can't figure out how to make it go away. There has to be something waiting for events and then dispatching them. Or each of your lwp's has to poll, doesn't it? Even select() in the Unix kernel does polling in some form. I just can't see how to avoid it. What am i missing? ron
andy@cbmvax.UUCP (Andy Finkel) (12/17/88)
In article <8812151937.AA17954@postgres.Berkeley.EDU> dillon@POSTGRES.BERKELEY.EDU (Matt Dillon) writes: > Now, who can figure out what I am talking about here? The idea is >for the initial call from main() to *create* a light weight process context Hmmm....sounds like coroutines >for the subroutine and to then allow main() to continue and call other >lwp's to create them. Thus, a direct call to task1() or task2() must return > startlwp() allocates a copy of the stack accessable to the subroutine. >Specifically, M+N bytes are allocated where M is the number of bytes already yup, we're talking coroutines here. > Not bad, eh? So, why would you want to do this when the amiga has >multitasking? Because this allows you to modularize your code without >the overhead of a task for each module! The context switch is much faster >than with real tasks. > > But the real reason is to get out of the main-loop driven standard >of programming (mac users are fond of quoting the fact that multitasking >does not avoid the need for a main-event-wait-loop). Here, one would > So, I need ideas on the waiting part (that waits for some even and >does the context switching). We need something that nicely complements >Wait(), but in the lwp sense. we call it cowait() You are well underway to reinventing coroutines, Matt :-) What are coroutines ? Well, they are pretty much as Matt is on the way to describing them...lightweight processes that operate synchroniously, using your stack. The Amiga file system uses them extensively. They are a pretty clever idea. They are also found in Workbench. I think Neil Katin was sufficiently impressed with the concept that he used that model of programming internal to Workbench. > -Matt -- andy finkel {uunet|rutgers|amiga}!cbmvax!andy Commodore-Amiga, Inc. "Possibly this is a new usage of the word 'compatible' with which I was previously unfamiliar" Any expressed opinions are mine; but feel free to share. I disclaim all responsibilities, all shapes, all sizes, all colors.