GARTHWILSON wrote:
I'm not sure this is the article I was thinking of, but I came across one of Brad Rodriguez's, "Forth Multitasking In a Nutshell," on paper in The Computer Journal, issue #58, Nov/Dec '92, and asked him if it was online since I couldn't find it there. He said he would try to find it on an old disc and convert it to html and post it, which he did in record time, at
http://bradrodriguez.com/papers/mtasking.html.
Interesting article and I'm pleased to note that he clearly differentiates between "multiuser" and "multitasking." The former is implicitly the latter, but not vice versa.
The Forth multitasking method, while efficient in the use of system resources, has a theoretical risk of deadlock if one of the tasks misbehaves. Avoidance of deadlock is one of the key benefits of using a jiffy IRQ to suspend the current task and restart another. However, an IRQ-driven task scheduler adds to the processing load and doesn't guarantee that any one task will meet required processing deadlines, often a requirement in Forth applications.
An IRQ-driven task scheduler isn't all that difficult to implement with the 65C816, thanks to the relocatable direct page and stack. Reentrancy is also relatively trivial, thanks to stack pointer relative addressing. Something similar could be accomplished with the 65C02 if the glue logic supports memory banking in a way that allows each task to have its own zero page and stack.
I've tinkered with a crude task scheduler on POC, which I had briefly incorporated into the firmware as an alarm function. I later removed it after deciding that I could better use the ROM space for other things. The principles are fairly basic:
- Push all registers to the stack, including DB and DP.
- Write SP to a "shadow" location that only the current task can access.
- Load SP from the new task's "shadow" location.
- Pull all registers from the stack in the reverse order of how they were pushed when the task was suspended.
- Execute RTI, which will restart the task.
Starting a new task involves setting the initial stack pointer, loading the stack with dummy values for the
.C,
.X and
.Y registers, loading the stack with the proper values for
DB,
DP,
PB and
PC, and then continuing at step 4 above.
Obviously, there's more to it, but the above covers the elemental parts.