The hardest project of the semester
This project was the hardest one I built that semester. MeongOS was our first attempt at an operating system, and the name was just as simple as it sounds: one teammate liked cats, so we put “meong” in the name.
The implementation was much less simple. Across four IF2230 milestones, our team had to move from a bootable kernel to interrupts, a filesystem, user programs, process management, and multitasking. Almost every layer introduced a concept we had never worked with before.
Before this assignment, an operating system felt closer to hardware than to an application someone had programmed. I knew it interacted with the machine, but I had not really considered that most of its behavior still had to be expressed as software.
MeongOS made that boundary visible. Some code spoke directly to disks, memory, interrupts, and processor structures, while the rest built useful abstractions on top. Features I had always taken for granted existed because someone had carefully programmed every step beneath them.
The filesystem opened my eyes
My main responsibility was the FAT-style storage layer and much of the shell, alongside bonus functionality such as recursive file operations and the clock. We pair programmed frequently, and I also helped finalize pieces across the system when integration brought everything together.
The filesystem became my favorite part because of how cleverly it uses limited space. A small amount of metadata can describe where content lives, how clusters connect, and which information belongs to a file or directory. It is information whose purpose is to organize other information.
Working on storage was a nightmare to say the least. We encountered faults and crashes that revealed almost nothing about the line that caused them, and a mistake in one part of memory could surface somewhere completely different.
I learned two practical debugging habits. For longer investigations, use a debugger and inspect the state properly. For a fast search, place early returns through the execution path until the failure disappears. That crude boundary helped us narrow down where memory first became invalid.
A file could disappear without being deleted
One memorable bug appeared in `rm`. From the shell, the file looked as though it had been removed correctly. Underneath, however, I had not cleared every piece of related filesystem state.
The shell considered the operation complete, but the leftover references eventually caused a fault. It taught me that changing what the user can see is not the same as restoring the system to a valid state.
Commands such as navigation, file creation, reading, copying, moving, deletion, and recursive search forced the shell to exercise nearly every filesystem operation. Relative paths needed their own state, while more involved file operations combined creation, deletion, and metadata updates.
System calls were difficult the first time because they crossed the boundary between user code and the kernel. Once the pattern became familiar, they started to feel natural. The shell turned low-level capabilities into something we could actually use and test.
Five teams, one house, and very little sleep
Near the deadline, we stayed at a friend's house where around five different teams were finishing the same difficult assignment. Each group worked on its own operating system, but everyone was struggling together in the same place.
Commit messages such as “jalan dong” captured the desperation better than formal documentation could. Integration was chaotic, and after multitasking finally worked on a day when I had slept for only about three hours, I did not celebrate for long. I simply went to sleep.
The moment that mattered most to me was when the filesystem and shell finally completed every CRUD operation correctly. Creating something was not enough. It also had to be found, read, updated, moved, and removed without corrupting the structures around it.
Somewhere around the first successful boot, we celebrated with pizza or martabak. I no longer remember which one, but I remember the relief of watching software we had built begin to behave like a tiny operating system.
If I rebuilt MeongOS today, I would begin with a better design instead of discovering the architecture while implementing it. The knowledge we gained would let us define clearer ownership, invariants, and boundaries before the first milestone became the foundation for everything else.
The lasting lesson is that memory is an integral part of a program and should be touched with caution. MeongOS also taught me to ask which responsibilities the kernel can provide and which still belong to us as programmers. The conveniences of modern software are not automatic. They are abstractions someone had to make reliable.
What stayed with me
- A file can disappear from the shell without being deleted correctly.
- Metadata is information that makes other information usable.
- The conveniences of an OS are software someone had to write.
- Memory deserves caution.
Run it locally
Clone the source repository or download the project ZIP, then run these commands from the project folder.
Explore the complete project
Download the source, assets, and included documentation as a ZIP archive.
Build and boot (Linux toolchain, via Docker on macOS)
$ docker run --rm --platform linux/amd64 -v "$PWD":/work -w /work debian:12 bash -lc \
$ 'apt-get update -qq && apt-get install -y -qq build-essential nasm grub-pc-bin grub-common xorriso genisoimage qemu-system-x86 gcc-multilib && make disk && make allandinsert'
Run
$ qemu-system-i386 -cdrom bin/os2024.iso -drive file=bin/storage.bin,format=raw,if=ide,index=0,media=disk
# On Linux the plain make targets work directly: make disk && make allandinsert && make run
Use it
# help lists twelve commands. Try:
# mkdir proj ; cp shell proj ; cd proj ; ls
# cp proj store -> copies the folder and everything inside it
# ps ; find shell ; clock





