Cooperative Multitasking on MSX

Por Matthew Sammut

Resident (49)

Imagen del Matthew Sammut

07-08-2017, 10:50

PS: How do I indent please?

I am using MSX Basic, and would like to implement a simple form of cooperative multitasking. The way I intend to accomplish this is using the following algorithm:

1. Initialize two global numeric variables SA and SB that will serve as stack pointers.
2. Initialize two more global variables IA and IB that will serve as instruction pointers.
3. Call the first subroutine (let's informally call it Task A) using GOSUB.
4. Set SA to the value of the current stack pointer.
5. Set IA to the value of current instruction pointer + 2 (to skip the GOSUB instruction on context switch).
6. Call the second subroutine "Task B" from within Task A using GOSUB.
7. Set SB to the new stack pointer.
8. Do stuff that will potentially take a very long time (infinite?) to execute.
9. After a number of instructions (approx. 250ms), I set IB to the point to the current instruction plus the number of instructions a context switch takes (maybe 2).
10. I perform a context switch by updating the stack pointer to the value in SA and the instruction address to the value in IA. Before setting the instruction address in the program counter, do I need to reload register values from the stack of Task A?
11. We're back in Task A.
12. Do stuff that will potentially take a very long time (infinite?) to execute.
13. After a number of instructions (approx. 250ms), I set IA to the point to the current instruction plus the number of instructions a context switch takes (maybe 2).
14. I perform a context switch by updating the stack pointer to the value in SB and the instruction address to the value in IB. Before setting the instruction address in the program counter, do I need to reload register values from the stack of Task B?
15. We're back in the middle of Task B (not at the start of the subroutine), and we can continue execution.
16. After some time perform context switch...yadda yadda yadda
...

Now, I have some questions.

1. Is this too far-fetched for MSX BASIC? Will it be easier in MSX C/Assembly code?
2. I am probably missing some stuff, can someone kindly point out what I left out or what may go wrong?
3. Before setting the instruction address in the program counter, do I need to reload register values from the stack of the other (currently suspended) task?
4. How do I get and set the stack and instruction pointers in MSX Basic?

Thanks Smile

Login sesión o register para postear comentarios

Por enribar

Paragon (1035)

Imagen del enribar

07-08-2017, 14:33

I don't understand what you want to do, but it smells like Modula's coroutines (?).

Por boomlinde

Resident (54)

Imagen del boomlinde

07-08-2017, 15:06

If you intend to implement this purely in BASIC you are going to run into trouble when you read or change the instruction pointer, because it will always point to the code you are currently executing.

Also consider a stackless design. Unless you absolutely need to yield execution to another thread from within a subroutine called by the currently executing thread, you don't need to keep track of different return stacks. Then you can implement your co-routines simply using GOTO to jump from one context to another when you feel like it. Unfortunately MSX BASIC doesn't seem to have indirect GOTO (i.e. GOTO [variable]) so each thread would be responsible itself for scheduling the next thread fairly.

If you are familiar with C, you should have a look at Protothreads (http://dunkels.com/adam/pt/expansion.html) -- It's a simple, stackless (neither data- nor return stack) co-routine implementation for C. It does this in plain ANSI C by abusing switch statements. Protothreads themselves decide when to yield to another thread or the caller. Maybe you can draw some inspiration for an implementation in BASIC, or you could try your hand at implementing it in C?

Either way, good luck, and I hope this helps!

Por hit9918

Prophet (2886)

Imagen del hit9918

08-08-2017, 16:08

to have asm codes in cooperative multitasking is easy

                ;-- app
                ...
                call switch     ;cooperative multitasking
                                ;"this function destroys all registers". cooperative is so easy.
                ...

                ;-- OS
switch:         ;the call to switch has pushed the current program counter on the stack
                ld hl,(otherthread)
                ld (otherthread),sp
                ld sp,hl
                ret     ;pop program counter of the otherthread

otherthread:    dw 0    ;stackpointer of the other thread

the little multitasking OS in 4 instructions lol.
this could be used in a game to make some low priority thread do some work distributed over multiple frames.

loop:
        ...
        do some work
        ...

        ld a,(vbiflag)  ;set by interrupt handler
        and a           ;test
        ld a,0
        ld (vbiflag),a  ;clear flag
        push bc
        call nz,switch  ;when there was an interrupt then switch over to high priority task
        pop bc
        djnz loop

Por bore

Expert (116)

Imagen del bore

08-08-2017, 17:19

One thing that could be worth considering is that if your high priority task is guaranteed to finish in a single frame you can do it entirely in the interrupt and let the CPU do the multitasking for you.
I mean, running high priority tasks is literally why the CPU have interrupts to begin with.
That way you won't have to waste cycles in the low priority task for it and can write it as if it would be the only task running.

The twistbar in baltak uses this method to unpack/precalc stuff needed later.
The main thread just sets up the interrupt that runs the twister, then it is free to do whatever it likes until it turns the twister off and moves on to the next part.

Por Matthew Sammut

Resident (49)

Imagen del Matthew Sammut

08-08-2017, 18:38

Quote:

when there was an interrupt then switch over to high priority task

What interrupts does call nz, switch handle? How do I send a hardware interrupt and handle it?

Por Manel46

Hero (534)

Imagen del Manel46

08-08-2017, 19:33

The interrupts are handled by the bios in # 38, where there are certain routines, but before returning, a hook is called in the system ram, in # FD9F, which contains # C9 (RET).
If you put a jump to your interruption routine, you already have it. In this position and the next two, of course.

Por Grauw

Ascended (8905)

Imagen del Grauw

08-08-2017, 20:06

Some more details, the two ISR hooks are called prior to the BIOS ISR, and when you install a hook make sure to also copy the old hook to RAM so that you can jump to it to return. Otherwise, you may introduce issues with ever-spinning diskdrives, etc. Also, it’s best to put your ISR in page 3, because page 0 will contain the BIOS and pages 1-2 could contain the DiskROM or something else, depending on when the interrupt triggers.

Article here: http://map.grauw.nl/articles/interrupts.php

It’s the lightest form of multitasking, easy to use but with some limitations. When your software is more demanding like e.g. threads that need to yield to eachother at various points and you want to keep the complete local state, some form of coroutines would be better suited.

Por Manel46

Hero (534)

Imagen del Manel46

08-08-2017, 20:34

Thank you grauw.
In our roms, I use the hook to call the routine that maintains the music and the FX, in the ROM itself, and that ends with a RET.
I did not know that the hook in the interruptions is first .