it seems to me that Arduino (at least the UNO) runs very simple hardware, does not have a full-fledged computer architecture (for example no paging of memory , no MMU, no context switches…). but how does the code work then? for example if a code writes to SD card, without an OS, it’s going to wait a long time for the IO instead of switching out for some other tasks to be done.
How does it do it?
It’s called “programming”.
Memory paging and MMU is irrelevant to the existence of an operating system; they are simply mechanisms for managing memory allocation. If sufficiently sophisticated, they do allow for inter-process isolation which has a certain importance in terms of reliability and security of multi-process systems, but that is not the basic definition of an operating system.
As westfw mentions, MSDOS did not really have those features - a (very) crude paging system on the 8086 expanded its memory space slightly.
The point really is - again, programming. A computer or microcontroller does what you tell it to do. If you tell it to wait for an SD card, well it will do that, but only because you were stupid enough to be “busy waiting”. Absolutely nothing prevents you from setting up the transfer, or a particular step of the transfer, and then doing something else while waiting for that step to complete. It is up to you, the programmer.
There is a level of competence in programming Arduinox. You can use the basic libraries provided and assemble them like Lego to perform simple linear tasks.
If you want to do more, then you have to work at a more fundamental level by knowing exactly what you are doing and not using “blocking” code such as “while” or delay(). The processor will just keep executing the instructions you feed it - it is up to you to feed it the right ones to get all of your jobs done.
Interrupts - which the Arduino does provide - are a part of this, though they are readily misunderstood by “newbies” who think they can be used to interrupt or alter the code flow. Of course, the very virtue of interrupts is to allow an alternate process to function without affecting the main code flow at all.