Memory Management – How MMU Works Without Virtual Memory in Kernel

memory

For my basic kernel I refuse to implement the dreaded confusion of virtual memory scheming, so I want only real memory addresses for everything. Some people have argued with me that since virtual memory is hardware supported, and sometimes the MMU hardware is integrated with the CPU, is that a rule that virtual memory is mandatory by hardware-design?

I have at least one good reason to not care about virtual memory at this point, being that the real memory I have is plenty (32 GB), and that I don't care to go through trouble and workarounds with virtual addressing and such(and that I honestly don't like the idea, regardless of the fact that it helps in many areas).

So if my question wasn't clear, is it possible to write a dedicated kernel and not at all implement virtual memory and use only real addresses for everything, with a MMU?

(Some of these low-level programming and hardware-concepts are mind-boggling, even after hours and hours of studying and implementing).

PS: I'm guessing the answer to some point is "yes", but hearing someone with more knowledge on the subject would definitely be a good pointer for me and others who read this.

Best Answer

You can certainly do that, but it may not save you as much complexity as you think.

One of the main benefits of virtual memory is that it keeps different processes from needing to know which parts of memory other processes are using.

On a system with virtual memory, one of the main jobs of the MMU is to give each process the illusion that the whole machine is theirs. As an example, if you have three programs you want to run on your OS, each can be compiled to start at memory address 0 -- and none of them has to be changed if you want to run all three at the same time. You are right, however, that this adds complexity to the kernel.

If you decide to save this complexity by making it the program's job to not use the same addresses used by other programs, there are two main ways you can go:

  • One option is to make each program use a different range of memory -- if program1 only uses addresses 0 .. 8191, and program2 only uses addresses 8192 .. 16383, then (barring a bug), they won't interfere with each other, but -- and this is a big deal -- you need to plan in advance what range of memory each program will use, so you need to know beforehand all the programs which will run on your OS. This may be workable for an embedded system (and may be the only choice there), but is obviously a show stopper for an OS which you hope other people will build software for. Historically, a method like this was also used for some early shared library implementations, and proved very hard to get right.

  • Another option is to compile each program using what's called position independent code -- code where no absolute addresses are compiled into the program, and all access to variables and code within the program is done by first checking a register (possibly the program counter) for a value pointing to where the program actually is in memory. This allows multiple programs to coexist, but requires the compiler and linker to do more work to make the program work this way, requires the operating system to do more work when loading each program, and has a cost in performance every time access to a variable or function has to be computed using this method. Historically, some operating systems, including classic MacOS before version 7.1, have taken this approach, and something like this is still used in modern shared library implementations.

So, you can move the complexity from the kernel to each running program, or from the kernel to your record-keeping of which program gets loaded where -- but you can't get rid of it altogether.

Related Topic