I'm trying to write an OS for the W65C02 CPU but i'm stuck because I have no idea on how to implement concurrency on such old CPU.
-
7One major problem with the 6502 architecture is the fixed nature of zero page and stack - your (cooperative) tasks need to either refrain from using them, or your context switches will be pretty expensive. Unless the hardware around the CPU supports some kind of bank switching to give each task their own zero and stack page, it will likely be not very usable.Hans-Martin Mosner– Hans-Martin Mosner2025-11-03 11:31:16 +00:00Commented Nov 3 at 11:31
-
2The concepts of implementing concurrency are the same be on an old microprocessor or a new one. The constraints may differ though. Due to the constraints of the 6502, co-operative multitasking might be the ‘better’ option.Kartman– Kartman2025-11-03 13:01:44 +00:00Commented Nov 3 at 13:01
-
4Also, please edit your question and add context. What is the exact hardware? Does it support banking? Do you have a regular source of IRQ/NMI? Would cooperative multitasking also work? What kind of OS did you envision? Something along the lines of MP/M? How do multiple users connect to your system? Etc.dirkt– dirkt2025-11-03 13:25:19 +00:00Commented Nov 3 at 13:25
-
3What kind of threads you want? Pre-emptive or cooperative? On which kind of hardware, as the CPU is not going to do pre-emptive scheduling unless it has a timer tick of some sorts. Please add details and then it can be answered. I know a person who did a pre-emptive multitasking environment in C, on a C64. Some assembly required, of course. Concurrency is same on all CPUs, it does not depend on it's oldness.Justme– Justme2025-11-03 14:03:31 +00:00Commented Nov 3 at 14:03
-
2Oh, and more to the point, is the question really 'only' about multiple threads (in a single application), or an OS running multiple independent programs at the same time?Raffzahn– Raffzahn2025-11-03 19:14:58 +00:00Commented Nov 3 at 19:14
1 Answer
There is not much room on a typical 6502-based system to multitask many different tasks at once without incurring a lot of runtime overhead, but basic multitasking can be done pretty well.
You'll need a routine that transfers control from one task to another; this is the crux of the matter. Each task will also need some place to store its context (read: the processor's internal registers will need to be saved somewhere, perhaps in the zero page, and then read back into the processor).
This routine, the task switcher, could be called from the tasks themselves, for cooperative multitasking*, or you could have a timer interrupt which calls the task switcher (preemptive multitasking).
Each task you want running will probably needs its own region of stack. You could split the stack page into four areas - one for each of your three tasks, leaving one for your multitasker - or some other such scheme.
It's not clear what exactly you're finding difficult about this, so forgive the vagueness of my answer here.
So let's say you have some small number of tasks. You could reserve an area of memory, having space for the context of each task. To suspend a task, you need to store the flags, A, X, Y, the stack pointer and the program counter to the corresponding area of memory. Then, to resume the task, you need to load A, X, Y, the stack pointer, the program counter and flags from the same region of memory. Doing this will mean that the corresponding task resumes. I would suggest initializing this table to a state where all tasks can be considered suspended. That can simplify the task switcher.
If that's your setup, then your multitasker just needs to suspend the running task, if any, and then resume the next task. A simple multitasker can simply switch between two tasks. More complicated, but still doable, is switching between an arbitrary number of tasks. More complicated still is managing task priorities, letting tasks sleep and wake back up. The possibilities are endless.
Depending on what you're doing, you could do variations on this theme. For example:
are you going to use IRQ to switch to the next task? In that case, you probably want to leave the flags on the top of your stack, instead of explicitly saving them to an area of memory.
Do you have a lot of tasks, or very deep callstacks? In that case, you might need to end up saving and restoring a task's stack itself as well, which will increase the overhead a lot. Then you might need to implement priorities, so that your most important tasks still get enough CPU time.
Are you going to use cooperative multitasking only? In that case, you can probably get away with saving/restoring much less data between task switches.
Can you make guarantees about what resources your tasks use? For example,
- it will simplify things a lot if your tasks never trample each others' memory, be that the zero page, the stack, or any other memory.
- Maybe you want to support stackless coroutines. It's trivial for these to task switch between each other cooperatively using a single
jmp.
Related considerations:
Since you mention that you're writing an operating system for the 65C02, you might want to consider the following:
- Interprocess communication:
- Unix has pipes, signals, shared memory and others. FreeRTOS has mutexes. Your multitasking operating system might want to implement the same.
- Ability to suspend a task while it's waiting for something
- For example, if one of your threads is waiting for a mutex, timer or read from stdin, then it has no need to be using the CPU. What FreeRTOS does in such a case it is stops trying to resume the task. FreeRTOS keeps track of when to resume a sleeping task.
-
1@lvd, there are a few different solutions to that (mutexes, bank switching, indexed addressing modes, or memcpy the shared region of memory during the context switch). But it's not clear to me that OP wants to run two identical threads each using zero page memory. Maybe it is not necessary. Hence why I said "It's not clear what exactly you're finding difficult about this, so forgive the vagueness of my answer here."Omar and Lorraine– Omar and Lorraine2025-11-03 15:42:49 +00:00Commented Nov 3 at 15:42
-
5@lvd The way to think about the 6502 from the perspective of larger CPUs is to see page 0 as a register bank. You save and restore registers as necessary upon a context switch. When I did multitasking, I reserved part of page 0 for interrupts and tasks, so I didn't have to save and restore the whole page. Any reentrant code had to restrict its page zero usage to the reserved area.John Doty– John Doty2025-11-03 17:20:04 +00:00Commented Nov 3 at 17:20
-
1@lvd surely your first comment depends on the quality of the loader? Just create an executable format with relocatable fixed zero page accesses. You probably need to be able to relocate code anyway if your hypothetical OS plans generic multitasking. So your two or more instances of the same executable work because each has been suitably adapted at the point it was loaded. I think this conversation is thinking too much about trying to add an OS around common 6502-style code rather than creating an environment to be coded to and tools to be programmed with.Tommy– Tommy2025-11-04 16:42:34 +00:00Commented Nov 4 at 16:42
-
1Heh, having so much replies here indeed shows how tricky is some generic multitasking for 6502 would be. Anyway, from some recent experience with 8-bit multitasking I have a strong belief that the way to do it is to switch memory spaces between tasks (by HW). Like the whole 64kb could (should?) be switched. It is no more 1982, nowadays even the simpler FatFS implementation would take ~10..20 Kb of code+buffers, while the RAM is cheap.lvd– lvd2025-11-12 10:41:01 +00:00Commented Nov 12 at 10:41
-
2@lvd Yes, but I would guess that most active development for the 6502 is on established hardware like the NES or C64 or whatever. Here we cannot really retrofit bank switching for zp/stack. But maybe something like the Commodore REU can help. And also, some 6502 varieties can actually move zero page and/or stack.Omar and Lorraine– Omar and Lorraine2025-11-12 14:57:43 +00:00Commented Nov 12 at 14:57