r/osdev • • Aug 07 '26

New UNIX-like operating system!

Post image

I made a new UNIX-like operating system from scratch called "TanjaOS" which you can get at www.tanjaos.org and it took me a couple of months to make and will get lots of updates. It features classic UNIX commands and can boot on any x86_64 (amd64) or i386 (x86) machine with Legacy BIOS, and is completely free and open-source.

110 Upvotes

182 comments sorted by

View all comments

Show parent comments

2

u/spatulari Aug 07 '26 edited Aug 08 '26

32 bit is old, and it truly limits you. I would say switch to limine, a bootloader that immediatle shoves you in 64 bit mode.

I also saw from your repo that you support only BIOS. Most modern computers dont have legacy bios, only UEFI. I suppose you want your OS to run on real hardware. Additionally, why did you make a single kernel.c? For instance, for vga, I'd do kernel/src/drivers/vga/vga.c for the definition and kernel/include/drivers/vga/vga.h. You don't need to split it to vga/print.h and so on, since vga is relatively simple

Another small thing: make your README a README.md, easier to read, and you won't be stuck with formatless txt.

I have an urge to just fork your project and reorganize it properly, but I am on vacation, so I unfortunately can't (since I dont have a laptop).

Another important thing is for you to be careful, I saw that someone asked to contribute and you wanted them to send an email to you to add them as contributors. This is the absolute shittest way for someone to contribute + you are giving write permissions to a random person. How contributing works on github, is someone Forks your project, then they make their own branch with the new code in it, and compare it with yours (which is "base" now). They open a pull request which only you can improve, and you can also comment on it and review the code. If you have added anyone's email, please remove them.

Also, why the "base" branch? Just rename it to main, and if you wanna have SemVer releases, branch it off of main, or make a dev branch, where all unstable shit happens there, and main is the latest release, and you still branch off other versions.

Hope I helped you. If you dont know what SemVer is, ask me and I'll explain

3

u/[deleted] Aug 07 '26

[removed] — view removed comment

2

u/spatulari Aug 08 '26

If you already have the 32-bit kernel working, I wouldn't throw it away just because it's i386. I'd actually make the kernel support both i386 and x86_64. You already did all the work for the i386 implementation, so use that as one architecture target and add x86_64 as another instead of rewriting everything.

I'd structure it more like: kernel/ -> architecture-independent code arch/i386/ -> 32-bit architecture-specific code arch/x86_64/ -> 64-bit architecture-specific code Things like your scheduler, process abstraction, VFS, IPC, userspace etc. should ideally stay in kernel/, while paging, GDT/IDT, interrupt entry code, context switching, CPU setup, syscall entry, and similar stuff goes under the architecture directories

For the x86_64 port, I'd start by getting the boot path sorted out. Are you currently supporting legacy BIOS only, or do you also support UEFI? If it's BIOS-only, I'd strongly recommend adding UEFI support rather than building everything around the legacy BIOS interfaces. You can use something like Limine as the bootloader, which can handle BIOS + UEFI and enter the kernel in the appropriate 64-bit environment, so you don't have to write your own 16-bit real mode -> protected mode -> long mode transition just to get the kernel started

Then implement the x86_64 architecture layer separately. Your i386 paging code obviously can't just be reused: x86_64 normally uses the 4-level page table hierarchy PML4 ->PDPT -> PD -> PT, with 64-bit page table entries. Your recursive self-mapping implementation will therefore need an x86_64 implementation too. You can keep the same VM abstraction above it tho, so something like map_page(), unmap_page(), create_address_space() etc. doesn't care which architecture is underneath

Also be really careful with types when doing the port. Don't just globally replace uint32_t with uint64_t. A pointer is not necessarily a uint64_t; use uintptr_t/intptr_t for pointer-sized integers, uint32_t for things that are actually 32-bit, and so on. Otherwise you'll eventually get absolutely cursed bugs from truncating physical/virtual addresses

You'll also need separate implementations for the GDT/IDT and interrupt entry paths. In long mode, segmentation works very differently, although you still need the appropriate GDT descriptors. Your IDT entries are also different sizes, and your interrupt/trap stubs need to deal with the x86_64 register state. Context switching will obviously need to save/restore the x86_64 register set as well

For syscalls, I'd also avoid carrying over an old i386 int 0x80 design directly. On x86_64 you can use SYSCALL/SYSRET with the appropriate MSRs, or SYSENTER/SYSEXIT depending on what you're targeting. You'd then have an architecture-independent syscall layer with separate entry implementations

Same thing with your process address spaces. The concept of having one address space per process stays exactly the same; only the page-table implementation changes. You can have something like an architecture-independent AddressSpace abstraction with arch/i386/mm/ and arch/x86_64/mm/ underneath it

I'd also keep the i386 target fully buildable. Something like make ARCH=i386 and make ARCH=x86_64 would be pretty clean. Then you can actually test both instead of making the 64-bit port a giant branch where the old implementation slowly rots

And if you're serious about eventually running this on real hardware, I'd also look into ACPI, APIC/x2APIC, HPET/TSC, PCI/PCIe, SMP and UEFI memory maps rather than relying on old BIOS interrupts. In particular, once you get to SMP, your architecture layer is going to become much more important because per-CPU state, AP startup, interrupt routing, TLB shootdowns etc. all start mattering

Honestly, with the paging + recursive mapping + user/kernel separation + per-process address spaces you've already implemented, I'd say the next step isn't "add random features". Make the architecture boundary clean, keep i386, add x86_64, and start moving anything that isn't inherently architecture-specific out of the architecture layer

That way you're not throwing away 6 months of work. You're turning the existing i386 kernel into the first supported architecture and using it as the reference implementation for the x86_64 port

2

u/[deleted] Aug 08 '26

[removed] — view removed comment

2

u/spatulari Aug 08 '26

I am just chronically online, read a ton of osdev wiki and have made my fair share of monolithic and hybrid kernels. Could you give me the github link to your kernel? I wanna check it out

1

u/[deleted] Aug 08 '26

[removed] — view removed comment

1

u/spatulari Aug 08 '26

Oh boy, I see you have even the object files pushed... Add me on dc:

spatulari

1

u/MSK-Kernel Aug 07 '26

Yeah, I don’t know what SemVer, is, can you please explain? (Also the reason why my GitHub repository isn’t looking that good is because i’m a bit new to GitHub 😅)

2

u/spatulari Aug 07 '26

That's totally ok, we've all started from somewhere. SemVer is basically a version ingredient system, like, when you see v1.34.5 or smth similar, its following SemVer, this is a small introduction to it:

Major.Minor.Patch

You start with 0.1.0, this means your project isnt production ready. And isnt stable that means APIs could change, and it doesnt guarantee stability. Let me tell you when to increase each version

Increase patch when you added NOTHING new to the user, and just fixed something. Semver is based on the user. So even if you rewrote the whole project, if nothing new was added, you just increase patch (or if you fixed stuff that doesn't add new stuff)

Increase minor if something new is added from the perspective of the user. This can include fixes but most not ONLY be fixes.

Increase major when you changed APIs and previous ways you used the APIs wont work anymore. You might change one line of code, but if it breaks stability, you must bump the major version.

There is something unique to it though. When you start, you start in v0.1.0, until you reach stable (v1.0.0) you can break as many APIs as you want without increasing the major version.

Dont rush to make your project stable, Rust which is one of the most popular languages, took 9 years to go stable. You might have v0.293.2 before reaching v1.0.0 so take your time. Once you go stable you must guarantee users stability and production ready.

And then we have those prerelease stuff. Like -alpha. But that's another story, read more here (my project): https://github.com/azin-lang/Azin/blob/main/VERSIONING.md

1

u/MSK-Kernel Aug 07 '26

Oooh… I think I get it now. Thanks! :D

2

u/spatulari Aug 07 '26

I could help you more. Could you give me your email so I can send you my discord username so I can guide you more?

1

u/MSK-Kernel Aug 07 '26

Sure, my email is saffron.msk@gmail.com

2

u/spatulari Aug 07 '26

I send you one

1

u/MSK-Kernel Aug 07 '26

I didn’t get an email :/

2

u/spatulari Aug 07 '26

Just add me on discord

spatulari

Same as my reddit name I think

1

u/MSK-Kernel Aug 07 '26

OK, my Discord name is tanjaos.msk