All the other solutions seem to assume something "real" OS-like.
You may also consider to start on real ground!
Maybe, you can start with some small experimental board with a controller, some RAM and some Flash memory, at least some primitiv I/O (e.g. input = interrupt, output = some LED). The Flash on a socket and some Flash burner device was beneficial.
Learn the basic startup machinery of a controller.
Learn the interrupt concept of the controller.
Learn how to address the I/O lines.
Based on that, you can write a primitive BIOS:
- startup and init the system
- install interupt handler
- do some lightshow controlled by the interrupt line
- program a timer for the lightshow
This all was very primitive:
- single threaded
- interrupt controlled dispatch loop
- no memory management
- *one* program from flash runs on the controller
To program this, you need some cross compiler development environment that compiles your source code into the machine code of your controller.
You would need to understand and control how to map the respective code/data sections into the flash. The startup procedure must load the pre-initialized global variables into RAM (scattering), init the I/O device/registers/..., allocate (reserve) some stack memory, etc.
Once you master this, you can go and write mutate your BIOS into a bootloader (e.g. if you have a serial line on your board):
- extend your BIOS by a serial line handler (read/write to some registers/memory mapped locations)
- extend your BIOS by a flash burning function (assuming you have the needed circuits on your board)
- extend your BIOS by a primitive shell that talks over the serial line handler
- implement some shell commands that allows to control the flash burning function
Now you can connect the board over serial line to a PC, run some terminal program on the PC to talk to the shell on the board. All this allows now to burn programs into flash.
You now can define some more commands on the shell in the bootloader: define some partitioning of the flash to store persitent data (flash drive). You now can also invent some structure in the flash drive (file system, e.g. some FAT). With the shell commands you can e.g. create the base structure (format) and add directories of files and directories, store data as files and access them through the directories. Invent some machinery to create/delete files and directories, store some data as files, append data to files (if this is desired), etc. All accessible over the shell.
Depending on your board, you are now open to add an LCD display, some I/O extensions, some IDE HD, some USB port, etc.
This all is still a very primitive system that lacks a lot of modern OS functions.
- No process concept to load and execute (and terminate) programs
- No memory management (all memory is globally shared)
- No concurrency other than interrupts (no threading with context switch, etc.)
- No whatever...
But fun to build it up