| Operating Systems Development Series | |
|
This series is intended to demonstrate and teach operating system development from the ground up. IntroductionWelcome! :) In the last tutorial we have finally covered hardware interrupts, implimenting interfaces for the PIC and PIT, set up exception handlers, and more. We have even re-enabled hardware interrupts! Yey! In this tutorial, we will be going over an even more complex topic: Paging. We cannot go much further without proper memory management. Because of this, I want to cover memory management in a deeper way. It is one of the most fundemental aspects of any computer system, and properly managing memory is important. So... Are you ready to enter the world of 32 bit virtualization? In this tutorial, We will cover:
Memory: A deeper lookRather then jumping directly into virtual memory and paging, I want to take a different approch here. That is, How can we even understand what virtual memory is without even understanding what memory itself is? With this, we cannot even begin discussing paging without knowledge of what virtual memory actually is. Because of this, we will first look at what physical memory is first. You know... Those little RAM chips inside of your computer? After looking at physical memory and how it works, we will then take a look at virtual memory, cool? Here we go...!Physical MemoryPhysical Memory: AbstractPhysical Memory is an abstract block of memory stored within the computers Random Access Memory (RAM). The way physical memory is "stored" within the RAM depends on the type of RAM the system uses. For example, Dynamic Random Access Memory (DRAM) stores each bit of data in its own capacitor that must be refreshed periodically. A capacitor is an electronic device that stores a current for a limited time. This allows it to either store a current (a binary 1), or no current (a binary 0). This is how DRAM chips store individual bits of data in a computer. Most of the time, the memory types (RAM, SRAM, DRAM, etc.) require a specific type of Memory Controller to interface with the processor and System Bus. The Memory Controller provides a way of reading and writing memory locations through software. The Memory Controller is also responsible for the constant refreshing of the RAM chips to insure they retain their information. Memory Controllers contain Multiplexer and Demultiplexer circuits to select the exact RAM chip, and location that refrences the address in the Address Bus. This allows the processor to refrence a specific memory location by sending the memory address through the address bus. ...This is where the software come in, as they tell the processor what memory address to read :) The Memory Controller selects the location within the RAM chip in a sequence manner. This means, if we access a physical memory location greater then the total amount of memory in the system, it will "wrap around" back to address 0. While this might seem good (Which it is in some ways), it isn't in other ways. For example, we could think we are writing to some location in memory, but--as it does not exist, we can be writing to a different area of memory. It is possible for memory holes to appear in the Physical Address Space. This can happen if, for example, a RAM chip is in slots 1 and 3, with no RAM chip in slot 2. This means that there is an area of memory between the last byte stored in the RAM at slot 1 and the first byte-1 in slot 3 that does not exist. Reading or writing to these locations have almost the same effect when reading or writing beyond memory. As memory is sequencal, reading or writing to a memory hole with have the effect of reading or writing to the next avilable memory block. This means, we run into the same problem form before: we can be reading or writing to a different location then what we expect. Because of this, it is important to get all of the information from the BIOS as you can, and never directly probe memory. Well, Thats all for what physical memory really is. Knowing how memory stores each bit you can probably start seeing where the bytes, words, dword, qwords, tbytes, etc.. start to come in. The most important of these are the byte, as that is the smallest data that the processor can access. But how does the processor know where a byte is located in memory? This is where the Physical Address Space comes in. Lets take a look :)Physical Address Space (PAS)This is the address space used by the processor (and translated by the memory controller) to refer to an 8 bit peice of data (ie, a byte) stored in physical memory (RAM). A Memory Address is just a number selected by the Memory Controller for a byte of data. For example, memory address 0 can refer to the first 8 bits of physical memory, memory address 1 can refer to the next 8 bits, and so on. The Physical Address Space is the array of these memory addresses and the actual memory that the memory addresses refer to. The physical address space is accessed by the processor through the systems address bus (Remember this from Chapter 7?) Okay, so lessee... the processor now can use an address to refer to a byte of memory. It useually starts from address 0, and increments for each byte in memory. Thats as simple as it can get! But, it still doesnt describe how the software can access memory. Sure, the processor itself now has a way of refrencing memory, but the software does not. The processor, depending on its needs, need to provide specific ways for software to provide software a way to refrence memory. Wait, what? Thats right... different ways of addressing and accessing memory...Addressing ModesThe Addressing Mode is an abstraction made by the processor to manage how the software accesses the Physical Address Space. This useually allows the software to set up the processors' registers so the processor knows how to refrence memory. We have already seen two: segment:offset memory addressing and descriptor:offset memory addressing. This is the interface given by the processor for the software to allow a way to access memory. We have covered the segment:offset addressing mode in Chapter 4 and the descriptor:offset memory addressing mode in Chapter 8.How Memory Works: DetailOkay, now lets look at memory in a new way. We have already covered alot of details on what memory is, address space and addressing modes. Now, lets put everything together, shall we? Alot of the information in this section is not needed, but I decided to include it for completness sake. Don't worry to much if you do not understand everything here. In Chapter 7, we have looked at a basic overview of a computer system and system architecture. We have talked about how the processor's system bus connects to the memory controller which is used to provide the system a way to control physical RAM. Kind of like this:The Need for VirtualizationThere are alot of big problems with directly accessing and using physical memory that you may already know (or even have experience with yourself ;) ) One that we have just seen was when we would access to a block of memory that does not exist. Knowing that both programs and data are in memory, it is also possible for programs to access each others memory spaces, or even corropt and overwrite themselves or other programs without knowing it. After all, there is no memory protection. Also, it is not always possible to load a file or program into a sequencial area of memory. This is when fragmentation happens. For an example, lets say we have 2 programs loaded. One at 0x0, the other at 0x900. Both of these programs requested to load files, so we load the data files:![]() Virtual MemoryIn order to better understand how virtual memory works, we should first have a better understanding of what virtual memory is. As you know, when we are not using paging, all of the addresses that we use to refrence physical memory are sent directly from the processor across the systems address bus to the memory controller. What does this mean? Anytime we refrence a memory location, the memory address is unmodified; it means we will refrence that exact location within the physical address space (PAS). In a system that supports "virtual addressing", things are a little different. On the x86 architecture, All memory addresses go through a Translation Lookaside Buffer (TLB) that sits between the memory controller and the processor. You have seen this before, remember? We will look at the TLB closer a little later, dont worry ;)Virtual Memory: AbstractVirtual Memory is a special Memory Addressing Scheme implimented by both the hardware and software. It allows non contigous physical memory to act as if it was contigius memory. Okay... So what exactally does this mean? This parts a little tricky. While a physical address is restricted to physical memory, a virtual address is not. This is an important concept. It allows a virtual address to be much larger then the amount of physical memory on the system. It also allows us to manage this much larger Virtual Address Space in a new way (as you will see very soon).Virtual Address Space (VAS)A Virtual Address Space is a Program's Address Space. One needs to take note that this does not have to do with Physical Memory. The idea is so that each program has their own independent address space. This insures one program cannot access another program, because they are using a different address space. Because VAS is Virtual and not directly used with the physical memory, it allows the use of other sources, such as disk drives, as if it was memory. That is, It allows us to use more "memory" then what is physically installed in the system. This fixes the "Not enough memory" problem. Also, as each program uses its own VAS, we can have each program always begin at base 0x0000:0000. This solves the relocation problems discussed ealier, as well as memory fragmentation--as we no longer need to worry about allocating continous physical blocks of memory for each program. Virtual Addresses are mapped by the Kernel trough the MMU. More on this a little later.Memory Management Unit (MMU)The Memory Management Unit (MMU) (Also known as Paged Memory Management Unit (PMMU)) sets between (Or as part of) the microprocessor and the memory controller. While the memory controller's primary function is the translation of memory addresses into a physical memory location, the MMU's purpose is the translation of virtual memory addresses into a memory address for use by the memory controller. This means--when paging is enabled, all of our memory refrences go through the MMU first!MMU Virtual AddressingThis is important! We will be revisting this a little later so do not worry if you do not understand this yet. :) A virtual address is a 32 bit address that follows the format (shown in binary form):Okay, okay, alot of this might seem confusing right now. Don't worry--everything will be explained a little later I promise :) For a little example, the virtual address 0xc0000000 will be the 768th directory index. the MMU will look at what physical address this translates to by looking at the 768 directory entry that we have set up. Once again, if you are a little lost dont worry--Alot of the info here will make alot more sense later on. For speed purposes, if the system suppots a TLB, the virtual address is first looked up in the TLB. If the MMU finds the page in the TLB (A TBL hit), the physical frame address is retrieved from the page. If there is no match, the MMU is required to look up the physical frame address from the page table. This is how the MMU retrives the physical address form the virtual address. If the hardware is unable to find the page, the physical address is invalid, or the page is not in physical memory, the MMU signals the processor to generate a page fault exception. Translation lookaside buffer (TLB)This is a cache stored within the processor used to improve the speed of virtual address translation. It is useually a type of Content-addressable memory (CAM) where the search key is the virtual address to translate, and the result is the physical frame address. If the address is not in the TLB (A TLB miss), the MMU searches through the page table to find it. If it is found in the TLB, it is a TLB Hit. If the page is not found or invalid inside of the page table during a TLB miss, the processor will raise a Page Fault exception for us. Think of a TLB as a table of pages stored in a cache instead of in RAM--as that is basically what it is. This is important! The pages are stored in page tables. We set up these page tables to describe how physical addresses translate to virtual addresses. In other words: The TLB translates virtual addresses into physical addresses using the page tables *we* set up for it to use! Yes, thats right--we set up what virtual addresses map to what. We will look at how to do this a little later, cool? Dont worry--its not that bad ;)Segmented Virtual Memory*edited out for incorrect content. We will re-add this section on the upcoming revision of this article. Dont worry too much--we arn't using this method. -MikePaged Virtual MemoryVirtual Memory also provides a way to indirectly use more memory then we actually have within the system. One common way of approching this is by using Page files, stored on a hard drive or a swap partition. Virtual Memory needs to be mapped through a hardware device controller in order to work, as it is handled at the hardware level. This is normally done through the MMU, which we will look at later. For an example of seeing virtual memory in use, lets look at it in action:![]() PAE and PSEPhysical Address Extension (PAE)PAE is a feature in x86 microprocessors that allows 32 bit systems to access up to 64 GB of physical memory. PAE supported motherboards use a 36 line address bus to achieve this. Paging support with PAE enabled (Bit 5 in the cr4 register) is a little different then what we looked at so far. I might decide to cover this a little later, however to keep this tutorial from getting even more complex, we will not look at it now. However, I do encourage readers to look into it if you are interested. ;)Page Size Extension (PSE)PSE is a feature in x86 microprocessors that allows pages more then 4KB in size. This allows the x86 architecture to support 4MB page sizes (Also called "huge pages" or "large pages") along side 4KB pages.The World of PagingLet the madness begin :)IntroductionWoo-hoo! Welcome to the wonderful and twisted-minded world of paging! With all of the fundemental concepts that we have went over already, you should have a nice and good grasp at what paging and virtual memory is all about. This is a great start, don't you think? Okay, cool...but, how do we actually impliment it? How does paging work on the x86 architecture? Lets take a look!PagesA Page (Also known as a memory page or virtual page) is a fixed-length block of memory. This block of memory can reside in physical memory. Think of it like this: A page describes a memory block, and where it is located at. This allows us to "map" or "find" the location of where that memory block is at. We will look at mapping pages and how to impliment paging a little later :) The i86 architecture uses a specific format for just this. It allows us to keep track of a single page, and where it is currently located at. Lets take a look..Page Entry FormatThe x86 architecture uses a specific format for page entries stored within a programs virtual address space. This allows us to test different properties of the pages, or to get their frame address in physical memory. Lets take a look...
Notice that 0x100000 is 4KB aligned? It ORs it with 3 (11 binary which sets the first two bits. Looking at the above table, we can see that it sets the present and read/write flags, making this page present (Meaning its in physical memory. This is true as it is mapped from physical address 0x100000), and is writable. Thats it! You will see this example expand further in the next few sections so that you can start seeing how everything fits in, so don't worry to much if you still do not understand. Okay, this is great as this setup allows us to keep track of a single page. However, it is useless by itself as a typical system will need to have alot of pages. This is where a page table comes in. The Page TableThe page table...hm...where oh where did we hear that term before? *looks one line up*. Oh, right ;) A Page Table is..well..a table of pages. (Surprised?) A page table allows us to keep track of how the pages are mapped between physical and virtual addresses. Each page entry in this table follows the format shown in the previous section. While it is a very simple structure, it has a very important purpose. The page table containes a list of all the pages it containes, and how they are mapped. By "mapping", We refer to how the virtual address "maps" to the physical frame address. The page table also manages the pages, weather they are present, how they are stored, or even what process they belong to (This can be set by using the AVAIL bits of a page. This may not be needed, it depends on the implimentation of the system.) Lets stop for a moment. Remember that a page manages 4KB of physical address space? By itself, a page is nothing more then a data structure that describes the properties of a specific 4KB region of physical memory. Because each page "manages" 4KB of physical memory, putting 1024 pages together we have 1024*4KB=4MB of managed virtual memory. Lets take a look at how its set up:![]() This is our format for a virtual address. So, when paging is enabled, all memory addresses will now follow the above format. For example, lets say we have the following instruction: Here, 0xc0000 will be treated like a virtual address. Lets break it apart: What we are now doing is an example of address translating. We are actually translating this virtual address to see what physical location it refers to. The page table index, 11000000b = 192. This is the page entry inside of our page table. We can now get the base physical address of the 4KB that this page manages. If this page is present (Pages present flag is set), all we need to do is access the pages frame address to access the memory. If this page is NOT present, then generate a page fault--The page data might be somewhere on disk. The page fault handler will allow us to copy the 4KB data for the page into memory somewhere and set the page to present and update its frame address to point to this new 4KB block of physical memory. Okay okay, I know. This little example of creating a fake "virtual address" might seem silly, but guess what? This is how its actually done! The actual format of a virtual address is a little bit more complex in that there are three sections instead of 2. However, if we omit the first section it would be exactally the same as our above example. I hope by now you are starting to see how everything fits together, and the importance of page tables. There are several different ways to impliment page tables. Okay, We can easily just use an array of pages, but there are alot of problems with this method. First: How do we know what pages belong where? What should we look for when searching for a page? How should we search for a page? How should we allocate a page table? Per process? Right about now you can probably see both the importance of page tables, and why they are important. Inverted Page Table (IPT)*To be added*Multilevel Page Table*To be added*Virtualized Page Table*To be added*Page SizeA system with smaller page sizes will require more pages then a system with larger page sizes. Because the table keeps track of all pages, a system with smaller page sizes will also require a larger page table because there are more pages to keep track of. Simple enough, huh? The i86 architecture supports 4MB (2MB pages if using Page Address Extension (PAE)) and 4KB sized pages. The important things to note are: Notice how page size may effect the size of page tables.Page Directory TableOkay... We are almost done! A page table is a very powerful structure as you have seen. Remember our previous virtual address example? e gave an example of a virtual addressing system where each virtual address was composed of two parts: A page table entry and a offset into that page. On the x86 architecture, the virtual address format actually uses three sections instead of two: The entry number in a page directory table, the page table index, and the offset into that page. A Page Directory Table is nothing more then an array of Page Directory Entries. I know I know... How useless and non-informative was that last sentence? ;) So, anyways, lets first look at a page directory entry. Then we will start looking at the directory table, and where it all fits in...Page Directory Entry FormatThe format of an entry in the page directory table is very close to that of the page entry format we looked at above. Because of this, they are pretty much interchangable, but their are some minor differences.
Understanding the Page Directory TableThe Page Directory Table is sort of like an array of 1024 page tables. Remember that each page table manages 4MB of a virtual address space? Well... Putting 1024 page tables together we can manage a full 4GB of virtual addresses. Sweet, huh? Okay, its a little more complex then that, but not that much. The Page Directory Table is actually an array of 1024 page directory entries that follow the format above. Look back at the format of an entry and notice the Page Table Base address bits. This is the address of the page table this directory entry manages. It may be easier to see it visually, so here you go:![]() Understanding PagingNote: All of the example code here is also inside of paging.inc inside of our bootloader inside of this tutorials' demo. Check it out! We are almost done!! I feel this chapter is already big enough, dont you? We have covered almost every aspect of paging, its components, virtual memory, and even physical memory! However, we still have not covered the most important part of paging: Implimenting it, and understanding paging itself and how it works. Lets look at that next!Putting everything together: How it worksI am to admit: There is alot of stuff in this chapter. I wanted to cover everything in this chapter, but still try to keep things understandable without getting to complex. Everything we have covered is completely useuless by themselves. We have not looked at how everything comes together yet. This is where everything starts to come together, and where I hope things start becoming clear.Virtual Addressing and Mapping AddressesWhen we enable paging, all memory refrences will be treated as a virtual address. Remember the format of a virtual address? This is the format of a x86 virtual address:This is very important! This tells the processor (And *us*) alot of information. The directory index portion tells us what index into the current page directory to look in. Look back up to the Directory Entry Structure format in the previous section. Notice that each directory table entry containes a pointer to a page table. You can also see this within the image again in that section. Because each index within the directory table points to a page table, this tells us what page table we are accessing. The page table index portion tells us what page entry within this page table we are accessing. ...And remember that each page entry manages a full 4KB of physical address space? The offset into page portion tells us what byte within this pages physical address space we are refrencing. Notice what happened here. We have just translated a virtual address into a physical address using our page tables. Yes, its that easy. No trickery involved. Lets look at another example. Lets assumed that virtual address 0xC0000000 was mapped to physical address 0x100000. How do we do this? We need to find the page in our structures that 0xC0000000 refer to -- just like we did above. In this case 0xC0000000 is the virtual address, so lets look at its format: Remember that the directory index tells us what page table we are accessing within the page directory table? So... 1100000000b (The directory index) = 768th page table. Remember that the page table index is the page we are accessing within this page table? That is 0, so its the first page. Also note the offset byte in this page is 0. Now, all we need to do is set the frame address of the first page in the 768th page table to 0x100000 and voila! You have just mapped 3GB virtual address to 1MB physical! Knowing that each page is 4KB aligned, we can keep doing this in increments of 4KB physical addresses. Here is a full example that maps the 3GB virtual address page table to 1MB physical address space. Notice how it works: Thats all!?? Yep, pretty much. As soon as paging is enabled, accessing address 0xC0000000 will now be accessing physical address 0x100000 thanks to our code. Identity MappingIdentity Mapping is nothing more then mapping a virtual address to the same physical address. For example, virtual address 0x100000 is mapped to physical address 0x100000. Yep--Thats all there is to it. The only real time this is required is when first setting up paging. It helps insure the memory addresses of your current running code of where they are at stays the same when paging is enabled. Not doing this will result in immediate triple fault. Here is an example. PAGE_TABLE_0 is the first page table. This is entry 0 of the directory table. Assuming that our code is located at 0x500, we will want to idenitity map the first page table before enabling paging. Knowing that there are 1024 pages in a page table, all we need to do is write 1024 pages in the table beginning with physical address 0, and, for each page, increment the physical address by 4096 bytes (Which is 4KB - Remember that each page handles 4KB of memory?)Thats all there is to it... ;) Now that we have idenitity mapped our address space it is now safe to enable paging. Lets take a look...!! Enabling PagingOnce you have identity mapped your current running area of memory, all that is left to do is to load your current page directory into the processors CR3 register. Afterwords, simply set the PG bit in the CR0 control register and thats all! :) For example:Thats all that is needed and paging is now enabled! Paging: Methods*I plan on adding to this section*Demand PagingLoader PagingAnticipatory PagingSwap PrefetchPrecleaningPage FaultsIf a virtual address is refrencing a directory entry or page that is marked not present, the processor generates a #PG exception (exception 14). It will also generate this exception if a process running in a user mode tries to write to a page marked read-only or any kernel-only page. The processor also raises this exception if any reserved bits are set in the page directory entry or page entry structures. In our current kernel, this means when a page fault is generated, the processor will invoke our page_fault() C++ exception handler. Remember from Chapter 15 that then processor also pushes an error code for us on this exception? Thanks to the way our HAL directly installs these interrupt handlers (Please see the HAL's setvect() routine), the processor calls our exception handler as-is:The pushed error code is stored in err. The error code has the following format:
Putting it Together: Demo*Demo coming soon! We will also be describing the C++ paging interface code within the demo here*ConclusionI am very glad to get this one done! We have covered alot of information and ground in this tutorial: Physical memory, Virtual Memory, Virtual addressing and translation, paging, methods, and more. With this tutorial, we are not out of the paging word yet! However, we can all saftely go to bed tonight knowing that we have a better understanding of it, how it works, and hot to work with it. See? Its not so bad :) Now that we have paging started, we can start diving into that of memory and heap management. We will be going back to the kernel itself again and impliment a basic heap manager for our kernel. This is just one of those things nobody likes doing, but is required. Don't worry, we will get back to the fun stuff soon enough :) Its not too bad though--Having a heap manager will greatly simplify some of the more complex tasks that lie ahead. I'll see you there ;)Until next time,
~Mike (); Questions or comments? Feel free to Contact me. |