While it's cool and all to write your own bootloader, this means you have to deal with a lot of the really ugly bits of x86/PC architecture... like the A20 gate, for example (if you don't know what that is, count yourself lucky).
Instead, I suggest using GRUB to boot your kernel image. It leaves you in 32-bit mode and a relatively sane state. It's not hard to write a loader file (in assembly) which contains the multiboot header and an entry point. Presumably you'll want to set up a basic C runtime environment and call your C "main" function.
Writing your own bootloader is actually an excellent exercise in getting to know the intricacies of the memory mapping on the PC architecture. After you're done you can always decide to go for a read-made one but rolling your own is definitely useful if you plan on writing your own OS.
NIH applies like always, but if you plan on taking control of the machine you might as well begin at the beginning. Rolling your own BIOS would be a step too far I think :)
I suppose it depends what you want to get out of it. There's certainly educational value in going as deep as you have the stomach for. But, for me at least, learning the specifics of how to get a processor on the PC architecture into a usable state gets tedious fast.
If I were to write a basic OS, I'd do it for one of the microcontroller kits that are available, e.g. http://www.pjrc.com/teensy/ or something similar with an ARM7 cpu. Having a cleaner CPU architecture than x86 is bound to save a lot of headache...
The biggest challenge I found was not to adapt to CPU architecture headaches but to get to the point where the OS was self hosting its development. That took considerably longer than I ever bargained for and at the time virtual machines were pretty much non-existent so djgpp was used to bridge the gap. An edit-compile-test cycle of several minutes + a hang or so quickly eats up the days.
An edit-compile-test cycle of several minutes + a hang or so quickly eats up the days.
That's why the micros are an interesting option, they're running on real hardware but can be flashed in seconds and debugged in realtime with a JTAG cable. Very convenient. But on the other hand they tend not to have much storage or network connectivity, so it depends on what you're interested in if they're the right choice. Blinking LEDs and controlling step motors is always fun, though!
Enabling A20 is just an or and an out opcode. It's all the stuff you have to do around it (because you're switching from 16 to 32 bit mode). For the purpose of making formerly lucky people now unlucky, here's some background on A20. Please correct any inaccuracies, it's been a long time since I've written toy OSes.
The Intel 8086 could only access 1MB of memory (using 20 bits). While this was great for the early 80s, it started to become a problem later on. This was fixed in the Intel 286 (which could address up to 16 MB of memory) but needed to be compatible with the 8086 at the time.
The 8086 had 20 individual gates or address lines numbers A0 - A19. This meant that there were different ways of referencing the same addresses in different numbering systems, most notably in hex. When you went over FFFFF, you'd actually wrap around. Some software relied on this (it was the 80s after all. For further examples of wrongness in the 80s see big hair and shoulder-padded suits).
For the 286 to access 16MB while enabling compatibility with the 8086 a kludge was invented. An extra logic gate (A20) was added to the keyboard controller that would enable or disable access to memory above 1MB. The bios enables it when it checks memory, then disables it when transferring it to the OS. The boot loader (normally) runs through a series of checks, reads the keyboard, makes sure the keyboard buffer is empty and sets the gate as part of switching into 32-bit mode on x86.
This still happens, even on your top of the range 64-bit quad core behemoth if you're running a 32-bit Operating System. I'm not sure whether this still applies to 64-bit Operating Systems as they were after my time, but if anyone here can tell me I'd love to know.
Personally I've always felt that the A20 part wasn't that hard - perhaps it's the wrong term but the hardest thing for me was all the checks for the right hardware, I kept finding stuff that works fine in bochs or qemu then barfs on real kit.
An extra logic gate (A20) was added to the keyboard controller that would enable or disable access to memory above 1MB.
Yes, the fact that it's on the keyboard controller is the single biggest WTF about the whole thing.
This still happens, even on your top of the range 64-bit quad core behemoth if you're running a 32-bit Operating System. I'm not sure whether this still applies to 64-bit Operating Systems as they were after my time, but if anyone here can tell me I'd love to know.
As far as I know, yes it's still part of the boot process even for operating systems that run in long mode (i.e, x86-64).
Personally I've always felt that the A20 part wasn't that hard - perhaps it's the wrong term but the hardest thing for me was all the checks for the right hardware, I kept finding stuff that works fine in bochs or qemu then barfs on real kit.
It's not so much that it's hard... if you're like me, you spend a good half an hour trying to make sense of what it is, then copy-paste some code to do it. I was just using it as an example of a seriously weird 'feature' of the architecture.
It's a bit of a niche inside a niche: it explains how to set things up to write an OS in D. This was extracted from the OS that my friends and I started a while ago, that's now two of theirs' PhD research:
If you're into building custom OS' but not interested in doing the lowest level bits (bootloader, fiddling page table bits, worrying about the processor details, etc), using a pre-existing microkernel and building whatever you want on top of it can be interesting.
I used my diploma thesis as an excuse to build a toy OS on top of a L4 (L4Ka::Pistachio) microkernel. It provides the basics, incl. IPC and VM building blocks, and a straightforward C/C++ API, and you can do the rest (there's also a number of things built on top of it that you could mix'n'match - i just implemented most of my userspace stuff because that was what I was interested in).
Some links (haven't been in touch for a few years, I don't know how up to date they are):
This is most definitely the best (existing) tutorial for getting started in kernel development. It explains how to implement a small unix-like kernel, focusing on interesting problems like virtual memory, multithreading and system calls rather than fiddly details of the x86 architecture from 20+ years ago.
There is no reason to write your own x86 bootloader- it's just an exercise in abstracting away the most brain-dead ISA on the planet.
What you should do instead is write the kernel and just load it with GRUB. It allows you to immediately jump into the interesting stuff that makes your kernel unique, makes it easy to test on real hardware without getting a second machine, and makes it possible to dual-boot with it if/when you get that far.
I haven't tried this, but it might be fun to write a kernel for some other system- some ARM device, maybe. Any kind of bootloader would be infinitely less encumbered with x86's layer upon layer of compatibility stuff.
This is where floppy disks come in handy. Burning an ISO to a CD for each round of testing is a pain and wasteful. When I dabbled into this years ago I was lucky enough to still have a floppy drive.
A great suggestion of course. I do admit that it didn't immediately spring to mind (or rather, didn't spring to mind at all until I read your response). Honest question: is it possible that you might run into difficulties getting your low-level OS to function properly in a virtualization, or has the tech come along enough to where this is essentially a non-issue?
In my experience VirtualBox works okay for low-level stuff, but it's impossible to debug with. qemu and bochs are much better tools since you can actually debug your kernel.
Instead, I suggest using GRUB to boot your kernel image. It leaves you in 32-bit mode and a relatively sane state. It's not hard to write a loader file (in assembly) which contains the multiboot header and an entry point. Presumably you'll want to set up a basic C runtime environment and call your C "main" function.
http://wiki.osdev.org/Bare_bones