
One of the most important considerations for your OS kernel is memory management. For all but the very simplest of environments, your kernel is going to need memory allocation and deallocation (free) facilities. This, of course, means carving off a “chunk” of memory each time somebody asks for some and managing it when they give it back. You also have the small, circular problem of allocating memory to hold the data structures your memory allocator needs to manage the memory it’s allocating!
Before we even get there, we have a more fundamental consideration. From whence does this memory come and how do we manage that? Well, the system has an amount of physical memory and the simplest model is to use it “as is” – which of course means referring to that memory via its physical addresses.
While this makes memory management a little easier, it causes some other complications for our OS. For starters, if we’re using physical addresses then we’re going to be constrained by the amount of physical memory installed in the system at run time. This might not seem like much of a problem at first, as pretty much every machine will have at least 4 MB and that’s plenty of space for a simple, toy kernel – and if you use GRUB to boot your kernel, you’ll probably end up loaded on the 1 megabyte boundary (physical address 0x100000) in a 32-bit “flat” protected mode.
However, having your hand up the skirt of the boot loader and making a load of assumptions about your environment isn’t wise. In the case of multiboot compliant boot loaders like GRUB, the multiboot specification doesn’t define your load address, or even a valid stack pointer, as part of the specification which means that these are officially undefined and you should write your code accordingly!
If we can’t be sure what our addresses will be, we’ll have to write code that is position independent. This is fine for loadable modules but might be a bit of a hassle if the entire kernel has to be this way.
Moreover, you may want to have more flexibility about how the memory address space appears to your kernel and, if you implement it, user land – it can be helpful to be able to reference a contiguous address space. This becomes much more important if you want to take advantage of other “virtual memory” features like extending the amount of apparent available memory by paging between memory and disk.
So virtual memory is the way to go?
Well, not necessarily. Mainstream operating systems that are “general purpose” or intended to be used across platforms of varying configurations typically need to implement virtual memory to provide a consistent, generic environment between systems. However, operating systems intended for very specific hardware architectures and/or embedded systems don’t always need this degree of flexibility.
Now let’s be honest – whatever lofty goals we have in mind, the reality is that the vast majority of “hobby operating systems” will never be that widely used and indeed, most will never make it further than your own eyeballs. Do you really need a full virtual memory implementation? You could just muddle on with physical memory only or, if the architecture supports it, use segmentation.
However, both of those approaches also have their disadvantages and besides, writing a toy kernel is as much a learning exercise as anything else – so for most of the other articles in the OS Development/Memory section, I’m going to assume you do want to implement a page-based virtual memory management system.