I poked around the website but it seems light on implementation details. Super-impressive piece of work though.
Edit to say: thank you to the responders who've backfilled the gaps in my memory. Interesting what you forget given enough time.
I poked around the website but it seems light on implementation details. Super-impressive piece of work though.
Edit to say: thank you to the responders who've backfilled the gaps in my memory. Interesting what you forget given enough time.
It can still be downloaded here : https://www.hpcalc.org/hp48/apps/shell/
You don't need MMU or anything of the sort.
The only thing you need is programmable interrupts.
My implementation was using a 64Hz timer, that was also synchronized on the LCD screen "vsync" as it had to be serviced for kernel-level double buffering and different modes of simulated multiple grey levels.
It is much less difficult than it seems, save registers, save context, load context, load registers, return to the current process.
Optimizing the code is just a good knowledge of the CPU constraints and bit of juggling with registers. It was a simpler time, you could benchmark code by counting cycles, instruction by instruction. No cache, no branch predictors, no throttling.
Those were good days writing demos and crack intros.
This line is typically tied in popular Z80 machines to the vertical blanking generator (frame refresh - 50/60Hz). Presumably, the OS would then schedule the tasks in a round robin fashion and most tasks will cooperate and yield control quicker to allow a not too choppy experience.
In general the only thing needed is an external interrupt (which the Z80 has). All but the cheapest deeply embedded 8 bit micros will have one and therefore can support preemption.
Back in the dark ages had an "OS" which had a simple "task table". This was a circular linked list with enough space after each pointer to store the CPU state for a task and some stack. There was one element per task. The task interrupt would fire every 100ms, the registers/stack pointer/PC would be dumped to that, the list followed and the next task picked up and loaded into the CPU, then it'd return from interrupt and off it'd go again. You could call YIELD at any point if you didn't want the rest of your time slice. If you had something that needed to handle an interrupt from the CTC or an NMI, it would disable interrupts until it was done.
Notably you didn't tend to create new tasks. They were hard coded in at assembly time!
Edit: we also had an 8031 version as well.
> Notably you didn't tend to create new tasks. They were hard coded in at assembly time!
Reminiscent of the "executive" in the Apollo lunar module computer.
You can find the documents on bitsavers, the binaries may well be there and/or on Gaby's site.
¹ https://ia903206.us.archive.org/19/items/Your_Sinclair_003/Y...
Of course with something like a MGT +D with the 8K ROM and 8K SRAM it would have been possible to use the NMI button to switch between two 48K Spectrums on a 128K and have them run a game in one and BASIC in the other.
Your original statement is sufficiently ambiguous that non-mind reading people might interpret it to mean “all tasks”.
Bank switching requires part of the address space to be common amongst all banks (never switchable) so code to manage the switching can execute. In that part of the address space you can do things like monkey patch the OS and driver entry points, or switch banks and collect or patch all the RAM.
https://retrocomputing.stackexchange.com/questions/7794/did-...
Sure you'll need to save the registers, but you could do that using the stack as well. The second register set just makes it quicker.