Due Thursday, April 8th, 2004 (9:00 AM)
The purpose of this project is to add paging to your project. This will require many small, but difficult changes to your project. More than any previous project, it will be important to implement one thing, test it and then move to the next one.
The first step is to modify your project to use page directories and page tables and segmentation rather than just segments to provide memory protection. To begin using page directories, every region of memory your access (both kernel and data segment) must have an entry in a page directory and table. The way this will work is that there will be a single page directory for all kernel only threads, and a page directory for each user process. In addition, the page directory for user mode processes will also contain entries to address the kernel mode memory. The memory layout for this is shown below.
|VA 0x0000 0000||Kernel Memory||
Start of kernel memory
(map all physical memory here)
|VA 0x8000 0000||User Memory||Data/Text start here
|VA 0xFFFF E000||User Memory||Initial stack at top of this page|
|VA 0xFFFF F000||User Memory||Args in this page|
|VA 0xFFFF FFFF|| ||Memory space ends here|
The kernel memory should be a one to one mapping of all of the physical memory in the processor (this limits the physical memory of the processor to 2GB, but this is not a critical limit for this project). The page directory/table entries for this memory should be marked so that this memory is only accessible from kernel mode (i.e. the userMode bit in the page directory and page table should be 0). To make this change, you should start by creating a page directory and page table entries for the kernel threads by writing a function that initializes the page tables and enables paging mode in the processor. You can easily do this in mem.c, in the function Init_Mem.
To setup page tables, you will need to allocate a page directory (via Alloc_Page) and then allocate page tables for the entire region that will be mapped into this memory context. You will need to fill out the appropriate fields in the page tables and page directories. The definition of paging tables and directories are to be found in paging.h (structs pageTableEntry and pageDirectoryEntry). Finally, to enable paging for the first time, you will need to call the routine Enable_Paging(pdbr) which is already defined for you in lowlevel.asm. It takes the base address of your page directory as a parameter. You should be able to do this step and test it by itself (by temporarily giving user mode access to the kernel pages - set the userMode field to 1).
The next step is to modify your user processes to all use pages in the user region. This is a two-step process. First, you need to allocate a page directory for this user space. You should copy all of the entries from the kernel page directory you've set up in Init_Mem. (This makes interrupt handling easy because you don't have to change the page tables when you switch back and forth).
Next you need to allocate page table entries for the user processes text and data regions. When you create the executable image don't include space for the stack (but do round to PAGE_SIZE, though). Allocate (a page at a time) as many pages as needed to hold the executable image. Then copy the image, page by page into the newly allocated pages. Do not forget to add an entry for each newly allocated page in your process' s page table.
Finally, you should allocate space for two pages of memory at the end of the virtual address range (i.e. the last two entries in the last page table). One is for the parameters, the other one is for stack. For the user space page mappings, make sure to enable the userMode bits in both the page directory and page table entries.
You will also need to change some aspects of how the code from project #2 sets things up. The base address for the user mode process should be 0x8000 0000, and the limit should be 0xFFFF FFFF. This will allow the user space process to think that its virtual location 0 is the 2GB point in the page layout and will greatly simplify your kernel compared to traditional paged systems. You will also need to add code to switch the PDBR register. For this, in Activate_User_Context_C you should add a call to setPDBR (provided for you in lowlevel.asm) as part of a context switch, after you load the LDT. Of course, you will add a field in the userContext structure that will store the address of the process's page directory).
You will also need to create a second version of Alloc_Page (in mem.c). This version should be called Alloc_Pageable_Page. The primary difference is that any page allocated by this routine should have a special flag PAGE_PAGEABLE set in the flags field of its entry in the struct Page data structure (see mem.h). All pages (but not page directories and page tables) for a user space process should be allocated using this routine.
At this point, you should be able to run programs again.
One of the key features of using paging is to have the operating system handle page faults. To do this you will need to write a page fault interrupt handler. The first thing the page fault handler will need to do is to figure out the address of the page fault. It will then need to determine an appropriate action to take. Possible reasons for a page fault, and the action to take are shown in the table below.
|Stack growing to new page||Fault is within one page of the current stack limit||Allocate a new page and continue.|
|Fault for paged out page||Bits in page table indicate page is on disk||Read page from paging device (sector indicated in PTE) and continue.|
|Fault for invalid address||None of the other conditions apply||Terminate user process|
paging.c already provides you with an empty page fault handler (PageFault_Handler). You need to register this as a handler for interrupt 14 in trap.c. In order to test the page fault handler, run the provided program rec.c.
The fault handler reads register cr2 to determine the faulting address. It also prints the errorCode field defined in the struct pageFaultErrorCode in paging.h
At some point, your operating system will run out of pages to assign to processes. In this case, you will need to pick a page to evict from memory and write it to the backing store (paging file). You should implement a version of pseudo-LRU (see section 10.4.4 in textbook). Use the reference bit in the page tables to keep track of how frequently pages are accessed. To do this, use the clock field in the struct Page in mem.h. You should update the clock on every page fault.
You will also need to manage the use of the paging file. The paging file consists of a group of consecutive 512 bytes disk blocks. Calling the routine getPagingFileInfo function in pfat.c, will return the first disk block number of the paging file and the number of disk blocks in the paging file. Each page will consume 8 consecutive disk blocks. To read/write the paging file, use the functions IDE_Read and IDE_Write.
When a page is paged out, you will need to update the page table entry for that page to clear the valid bit. You will probably want to put the first disk block that contains the page into the pageBaseAddr field of the page table. The kernelInfo bits (3 bits holding a number from 0-7) can be used to indicate that the page is on disk rather than not valid. You should also invalidate the TLB as part of the page out operation. Although the x86 processor supports a selective invalidate, you can invalidate the entire TLB for this project. Any move to cr3 (the PDBR) will flush the TLB. To do this, use the routine flushTLB provided in lowlevel.asm
When you bring a page in off disk, you may discard its disk version (i.e. free the disk space used by the page). This will simplify your paging system, but will require that when a page is removed from memory it must always be written to the backing store (since even clean pages no longer have a version on disk).
As part of process termination, you will need to free the memory associated with a process. This includes freeing the pages used by the process, freeing the page tables and page directories. In addition, you will need to release the backing store space used by any pages of the terminating process.