OS classification and design suggestions for my OS
Posted: Tue Jan 14, 2020 1:28 am
Hi all,
So I was just reading some of the (very interesting) topics in this forum and thought I might pose a question of my own. My kernels design is one that I would personally classify as an "exokernel" and here's why. My kernel is literally a small program linked to a library. The program depends on the library, but that could be easily unlinked. What the kernels library *doesn't* do is set up rusts memory allocator so it can use a heap that I've mapped to it; that must be done by the program.
To expound on this further, I must describe how Cargo (Rusts package manager) works. When I run "cargo xrun" to run my kernel (not cargo run, that builds for the "host", which I don't have), cargo searches for one of two files:
But what happens if you have both main.rs and lib.rs in the same src directory? You have a binary library combination. Cargo (and Rust) treat this as two separate crates with the same name. I believe, though am not precisely sure, that Cargo compiles both simultaneously, and lets the compiler figure out what's what. But I digress.
My kernel binary (my little program) only has two parts to it; the rest is in the kernel library: VGA support and kernel initialization and panic handling. If I wanted to, I could move VGA support into the library too, thereby making the binary even more compact and only doing panic handling. As such, my kernel does the following when it boots, right now:
1) Checks if processor has RDRAND support; if no, stop booting process.
2) It calls kernel::memory::init to set up the initial memory heap (the bootloader I'm using handles the setting up of paging and entering of long mode for me). The kernel:: part is it calling outto the library.
3) It sets up a program allocator, since one does not exist. At this point we have a heap but we can't use it because Rust doesn't know how to do that yet. We do that through the global_allocator attribute (that part is done at compile time). Then we tell the memory allocator the address and size of the heap and it sets it up internally (I use a slab allocator for my kernel).
4) We then call kernel::init(). Again, we're calling out to the library for this one. This is where all the initialization magic happens. In serial, this function does the following (I don't have SMP or multitasking yet):
a) the GDT is initialized, setting asside a small stack for the double fault handler;
b) The IDT is configured, the chained PICs are set up, and interrupts are remapped;
c) Interrupts are enabled;
d) interrupts are immediately disabled to allow the RTC to be configured, in case an interrupt has been received between the time the IDT was configured and the time that interrupts are enabled, and then interrupts are immediately re-enabled;
e) The PCI bus is scanned and an internal device list is constructed; and
f) The various drivers are initialized (for now, only the keyboard and ATA controller); and
5) the kernel enters an infinite hlt loop.
I could probably shuffle most of this around and make it better, even faster, but right now I'm just focused on trying to get my kernel somewhere. Panic handling and allocator failures are configured at compile-time too, making things a bit easier for me. I could probably split the kernel into smaller libraries too, i.e.: each driver "section" (i.e. filesystems, networking, ...) into their own libraries, but I like the current design. It wouldn't be too hard for anyone to turn my little kernel into a small bootstrap loader or, really, anything else; they'd just need to replace the library with their own one.
So... what would you classify my kernel as? Do you have any suggestions on how I could improve the design that I have so far? My keyboard driver is one of the parts I'm proud of because its almost entirely interrupt based, with only things like direct PS/2 controller commands being blocking. So, the driver, when it is initialized, can initiate a self test and then immediately allow its init function to terminate. It knows what's happening because my interrupt controller -- which is in the library, mind, along with literally everything else -- just calls out to it and says, "hey, this happened, handle it".
Right now, as many people probably know, I'm focusing on getting EXT2 working (as well as HDA). HDA is up in the air right now because I'm waiting to see if my HDA code is wrong or my Qemu install is faulty, so I'm just proceeding onto filesystems and disks because there's not much I can do with HDA at this point. As always, the code is here. I don't have a name for it -- I've left that until the end because this is purely educational for me right now.
So I was just reading some of the (very interesting) topics in this forum and thought I might pose a question of my own. My kernels design is one that I would personally classify as an "exokernel" and here's why. My kernel is literally a small program linked to a library. The program depends on the library, but that could be easily unlinked. What the kernels library *doesn't* do is set up rusts memory allocator so it can use a heap that I've mapped to it; that must be done by the program.
To expound on this further, I must describe how Cargo (Rusts package manager) works. When I run "cargo xrun" to run my kernel (not cargo run, that builds for the "host", which I don't have), cargo searches for one of two files:
- main.rs: looked for (for building binaries)
- lib.rs: looked for (for building libraries)
But what happens if you have both main.rs and lib.rs in the same src directory? You have a binary library combination. Cargo (and Rust) treat this as two separate crates with the same name. I believe, though am not precisely sure, that Cargo compiles both simultaneously, and lets the compiler figure out what's what. But I digress.
My kernel binary (my little program) only has two parts to it; the rest is in the kernel library: VGA support and kernel initialization and panic handling. If I wanted to, I could move VGA support into the library too, thereby making the binary even more compact and only doing panic handling. As such, my kernel does the following when it boots, right now:
1) Checks if processor has RDRAND support; if no, stop booting process.
2) It calls kernel::memory::init to set up the initial memory heap (the bootloader I'm using handles the setting up of paging and entering of long mode for me). The kernel:: part is it calling outto the library.
3) It sets up a program allocator, since one does not exist. At this point we have a heap but we can't use it because Rust doesn't know how to do that yet. We do that through the global_allocator attribute (that part is done at compile time). Then we tell the memory allocator the address and size of the heap and it sets it up internally (I use a slab allocator for my kernel).
4) We then call kernel::init(). Again, we're calling out to the library for this one. This is where all the initialization magic happens. In serial, this function does the following (I don't have SMP or multitasking yet):
a) the GDT is initialized, setting asside a small stack for the double fault handler;
b) The IDT is configured, the chained PICs are set up, and interrupts are remapped;
c) Interrupts are enabled;
d) interrupts are immediately disabled to allow the RTC to be configured, in case an interrupt has been received between the time the IDT was configured and the time that interrupts are enabled, and then interrupts are immediately re-enabled;
e) The PCI bus is scanned and an internal device list is constructed; and
f) The various drivers are initialized (for now, only the keyboard and ATA controller); and
5) the kernel enters an infinite hlt loop.
I could probably shuffle most of this around and make it better, even faster, but right now I'm just focused on trying to get my kernel somewhere. Panic handling and allocator failures are configured at compile-time too, making things a bit easier for me. I could probably split the kernel into smaller libraries too, i.e.: each driver "section" (i.e. filesystems, networking, ...) into their own libraries, but I like the current design. It wouldn't be too hard for anyone to turn my little kernel into a small bootstrap loader or, really, anything else; they'd just need to replace the library with their own one.
So... what would you classify my kernel as? Do you have any suggestions on how I could improve the design that I have so far? My keyboard driver is one of the parts I'm proud of because its almost entirely interrupt based, with only things like direct PS/2 controller commands being blocking. So, the driver, when it is initialized, can initiate a self test and then immediately allow its init function to terminate. It knows what's happening because my interrupt controller -- which is in the library, mind, along with literally everything else -- just calls out to it and says, "hey, this happened, handle it".
Right now, as many people probably know, I'm focusing on getting EXT2 working (as well as HDA). HDA is up in the air right now because I'm waiting to see if my HDA code is wrong or my Qemu install is faulty, so I'm just proceeding onto filesystems and disks because there's not much I can do with HDA at this point. As always, the code is here. I don't have a name for it -- I've left that until the end because this is purely educational for me right now.