-
Notifications
You must be signed in to change notification settings - Fork 12
Paging #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Paging #29
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
b4b35aa
identity paging and vaddr translation
Sploder12 58bbff6
Nicer interface
Sploder12 9773714
More granularity
Sploder12 c39404c
tests
Sploder12 6eb84de
format
Sploder12 9d8f63b
another test and const
Sploder12 e264b25
ran formatter
Sploder12 2618a50
docs
Sploder12 ef089cb
Move macros
Sploder12 26be832
formater
Sploder12 ae2a676
comments + no binary
Sploder12 b94b1d8
missed a comment
Sploder12 55b5ddb
at&t syntax
Sploder12 d4654b9
small changes
Sploder12 6bbedcc
Add page fault test
Sploder12 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| # Paging | ||
|
|
||
| ## MMU and You | ||
|
|
||
| The Memory Management Unit (MMU) is responsible for translating "virtual" addresses to "physical" addresses. A "physical" address is an address that maps directly to a system's memory. On the other hand, a "virtual" address is an address that indirectly maps to a system's memory. When addresses are identity mapped, a virtual address is equivalent to a physical address (in that context). | ||
|
|
||
| The MMU will always translate addresses once it is enabled (via `cr0`). However, this creates overhead for one of the most frequent operations. To minimize this overhead, the MMU maintains a cache referred to as the Translation Lookaside Buffer (TLB). Since the TLB doesn't constantly monitor if the translation tables are modified, it must be invalidated whenever a change is made. This takes the form of either `mov eax, cr3; mov cr3, eax` or `invlpg eax`. The first essentially sets the active translation context to itself, while `invlpg` is a dedicated instruction for invalidating just 1 page (although it may invalidate more). | ||
|
|
||
| ## Page Directory | ||
|
|
||
| A Page Directory is a translation context. There can be many, and multiprocess operating systems may give each process its own page directory. However, only one is active at a time, the address of which can be found in `cr3`. The structure itself is 4KiB and consists of 1024 4-byte entries (for 32-bit x86). Each entry consists of 20-bits of address pointing to a page aligned Page Table, several flags, and some available bits. The important flags are Present, Accessed, Cache Disable, Write Through, Read/Write, User/Supervisor. | ||
|
|
||
| - Present signifies that the table exists, if this is false, when the MMU tries to access this entry it will cause a page fault. | ||
| - Accessed is set to 1 whenever the MMU accesses that entry, this value should be reset by the OS if the OS intends to use it. | ||
| - Cache Disable, as the name implies, prevents the entry from being cached into the TLB. | ||
| - Write Through is when data is written to both cache and main memory at the same time, when this flag is 0 write back is used instead. Write back is when cache is used until the cache is invalidated where it then writes to main memory. | ||
| - Read/Write and User/Supervisor are permission flags that control who and what is allowed to interact with certain pages. | ||
|
|
||
| ## Page Table | ||
|
|
||
| A Page Table is what determines which physical address is mapped to a virtual address. There are 1024 per page directory, and similarly the structure itself is 4KiB. The structure MUST be page aligned (since the page directory only stores the top 20-bits). The structure consists of 1024 4-byte entries (for 32-bit x86). Each entry consists of 20-bits of address pointing to a 4KiB page in physical memory, several flags, and some available bits. The important flags are the same as the Page Directory, with the addition of the Dirty bit. The dirty bit is set whenever the page is written to, the OS should reset this bit if it wishes to use it. | ||
|
|
||
| ### Virtual Address Decomposition | ||
|
|
||
| A virtual address is comprised of 3 parts, a table index, a entry index, and an offset. The table and entry indexes are 10-bits giving them a range of [0, 1024). The offset is 12-bits, which is [0, 4096). | ||
|
|
||
| The following diagram shows a decomposition. The construction of a physical address requires having a page directory populated with tables. | ||
|
|
||
|  | ||
|
|
||
| ### Mappings | ||
|
|
||
| (In the context of a single directory) | ||
|
|
||
| Identity mapping is when virtual addresses are the same as physical addresses. | ||
|
|
||
| 1:1 mapping is when each virtual address maps uniquely to a physical address. Identity mapping is a 1:1 mapping. | ||
|
|
||
| N:1 mapping is when multiple virtual addresses map to a phsyical address. | ||
|
|
||
| 1:N mapping can only occur with multiple directories. | ||
|
|
||
| #### Further Reading | ||
|
|
||
| [Paging](https://wiki.osdev.org/Paging) | ||
| [Page Tables](https://wiki.osdev.org/Page_Tables) | ||
| [Identity Paging](https://wiki.osdev.org/Identity_Paging) | ||
| [MMU](https://wiki.osdev.org/Memory_Management_Unit) | ||
| [TLB](https://wiki.osdev.org/TLB) | ||
| [invlpg](https://www.felixcloutier.com/x86/invlpg) | ||
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| #include "paging.h" | ||
|
|
||
| #include "stdlib/string.h" | ||
|
|
||
| // room for 3 page tables (12 MiB of mapped memory) | ||
| #define IDENTITY_PT_BASE 0x92000 | ||
| #define IDENTITY_PT_LIMIT 0x95000 | ||
| #define TABLE_COUNT ((IDENTITY_PT_LIMIT - IDENTITY_PT_BASE) / 0x1000) | ||
|
|
||
| #define MiB4 0x400000 | ||
|
|
||
| PageDirectory *idendirectory = (PageDirectory *)(ID_PAGE_DIRECTORY_BASE); | ||
|
|
||
| bool pageTablePresent(PageDirectoryEntry tableEntry) { | ||
| return tableEntry & ENTRY_PRESENT; | ||
| } | ||
|
|
||
| bool pageEntryPresent(PageTableEntry entry) { | ||
| // mask out all but first bit | ||
| return entry & ENTRY_PRESENT; | ||
| } | ||
|
|
||
| void setEntryAddr(PageTableEntry *entry, const void *addr) { | ||
| if (entry == NULL) | ||
| return; | ||
|
|
||
| *entry = ((uint32_t)(addr)&ENTRY_ADDR) | (*entry & ~(ENTRY_ADDR)); | ||
| } | ||
|
|
||
| void setActivePageDir(PageDirectory *dir) { | ||
| if (dir == NULL) | ||
| dir = idendirectory; | ||
|
|
||
| __asm__ volatile("mov %0, %%cr3" : : "a"(dir)); | ||
| } | ||
|
|
||
| PageDirectory *getActivePageDir(void) { | ||
| PageDirectory *dir = NULL; | ||
|
|
||
| __asm__ volatile("mov %%cr3, %0" : "=r"(dir)); | ||
|
|
||
| return dir; | ||
| } | ||
|
|
||
| void resetTLB(void) { | ||
| // setting the active directory to the current updates the TLB | ||
| setActivePageDir(getActivePageDir()); | ||
| } | ||
|
|
||
| #define PAGE_TABLE_OFFSET 22 | ||
| #define PAGE_ENTRY_OFFSET 12 | ||
|
|
||
| // highest 10 bits | ||
| uint16_t vaddrDirectoryIdx(const void *vaddr) { | ||
| return (uint32_t)(vaddr) >> PAGE_TABLE_OFFSET; | ||
| } | ||
|
|
||
| // middle 10 bits | ||
| uint16_t vaddrEntryIdx(const void *vaddr) { | ||
| // shifted right 12 then 10-bit mask | ||
| return ((uint32_t)(vaddr) >> PAGE_ENTRY_OFFSET) & 0x3ff; | ||
| } | ||
|
|
||
| // low 12 bits | ||
| uint16_t vaddrOffset(const void *vaddr) { | ||
| // 12-bit mask | ||
| return (uint32_t)(vaddr)&0xfff; | ||
| } | ||
|
|
||
| void *toVaddr(uint16_t dirIdx, uint16_t entryIdx, uint16_t offset) { | ||
| uint32_t vaddr = offset; | ||
| vaddr |= (uint32_t)(entryIdx) << PAGE_ENTRY_OFFSET; | ||
| vaddr |= (uint32_t)(dirIdx) << PAGE_TABLE_OFFSET; | ||
| return (void *)(vaddr); | ||
| } | ||
|
|
||
| PageDirectoryEntry *vaddrDirEntry(PageDirectory *directory, const void *vaddr) { | ||
| if (directory == NULL) | ||
| directory = getActivePageDir(); | ||
|
|
||
| uint16_t tableidx = vaddrDirectoryIdx(vaddr); | ||
| return &directory->entries[tableidx]; | ||
| } | ||
|
|
||
| PageTableEntry *vaddrTableEntry(PageDirectory *directory, const void *vaddr) { | ||
| // this will never be null (unless something really bad happened) | ||
| PageDirectoryEntry *dirEntry = vaddrDirEntry(directory, vaddr); | ||
| PageTable *table = (PageTable *)((*dirEntry) & ENTRY_ADDR); | ||
|
|
||
| if (table == NULL) | ||
| return NULL; | ||
|
|
||
| uint16_t entryidx = vaddrEntryIdx(vaddr); | ||
| return &table->entries[entryidx]; | ||
| } | ||
|
|
||
| void *vaddrToPaddr(PageDirectory *dir, const void *vaddr) { | ||
|
|
||
| if (dir == NULL) | ||
| dir = getActivePageDir(); | ||
|
|
||
| // get and verify page entry | ||
| PageTableEntry *entry = vaddrTableEntry(dir, vaddr); | ||
| if (entry == NULL) | ||
| return NULL; | ||
|
|
||
| uint32_t paddr = vaddrOffset(vaddr); | ||
|
|
||
| // apply offset | ||
| return (void *)(paddr + ((*entry) & ENTRY_ADDR)); | ||
| } | ||
|
|
||
| // identity maps the entire table at directory entry idx | ||
| void identityMapTable(PageDirectory *directory, uint16_t idx, uint32_t flags) { | ||
| PageTable *table = (PageTable *)(directory->entries[idx] & ENTRY_ADDR); | ||
|
|
||
| // 4GiB per directory | ||
| // 4MiB per table | ||
| uint32_t baseAddr = idx * MiB4; | ||
|
|
||
| for (uint32_t page_idx = 0; page_idx < PAGE_ENTRY_COUNT; ++page_idx) { | ||
| PageTableEntry entry = flags & ~(ENTRY_ADDR); | ||
|
|
||
| // 4KiB per entry | ||
| entry |= (baseAddr + page_idx * PAGE_SIZE) & ENTRY_ADDR; | ||
| table->entries[page_idx] = entry; | ||
| } | ||
| } | ||
|
|
||
| // preconditions, idx < PAGE_ENTRY_COUNT, table is 4KiB aligned | ||
| void addTableToDirectory(PageDirectory *directory, uint16_t idx, | ||
| PageTable *table, uint32_t flags) { | ||
| PageDirectoryEntry entry = flags & ~(ENTRY_ADDR); | ||
| entry |= (uint32_t)(table)&ENTRY_ADDR; | ||
| directory->entries[idx] = entry; | ||
| } | ||
|
|
||
| void initPaging(void) { | ||
| // clear the memory (essentially say no page tables exist) | ||
| memset(idendirectory, 0, PAGE_ENTRY_COUNT * sizeof(PageDirectoryEntry)); | ||
|
|
||
| // identity map 12MiB and setup directory | ||
| for (uint16_t idx = 0; idx < TABLE_COUNT; ++idx) { | ||
| PageTable *addr = (PageTable *)((idx * PAGE_SIZE) + IDENTITY_PT_BASE); | ||
| memset(addr, 0, PAGE_ENTRY_COUNT * sizeof(PageTableEntry)); | ||
| addTableToDirectory(idendirectory, idx, addr, DEFAULT_ENTRY_FLAGS); | ||
| identityMapTable(idendirectory, idx, DEFAULT_ENTRY_FLAGS); | ||
| } | ||
|
|
||
| setActivePageDir(idendirectory); | ||
|
|
||
| // enable paging flags in cr0 | ||
| __asm__ volatile("mov %cr0, %eax \n\t" | ||
| "or $0x80000001, %eax\n\t" | ||
| "mov %eax, %cr0"); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| #ifndef PAGING_H | ||
| #define PAGING_H | ||
|
|
||
| #include <stdbool.h> | ||
| #include <stdint.h> | ||
|
|
||
| // unfortunately C's bitfields can't provide the packing we need | ||
| typedef uint32_t PageDirectoryEntry; | ||
| typedef uint32_t PageTableEntry; | ||
|
|
||
| /*The page directory and it's tables must be 4KiB aligned (0x1000) | ||
| * 0x90000 is the start of the stack, in other words, | ||
| * the areas from 0x1000 to 0x90000 are in use! | ||
| * But there is a nice open region we can use from 0x91000 - 0x9f000 | ||
| * (We technically have until 0x9fc00 before we enter ExBIOS data) | ||
| */ | ||
|
|
||
| #define ID_PAGE_DIRECTORY_BASE 0x91000 | ||
|
|
||
| #define PAGE_ENTRY_COUNT 1024 | ||
| #define PAGE_SIZE 0x1000 | ||
|
|
||
| // The entries share flags! | ||
| #define ENTRY_PRESENT 0b000000001 | ||
| #define ENTRY_RW 0b000000010 | ||
| #define ENTRY_US 0b000000100 | ||
| #define ENTRY_PWT 0b000001000 | ||
| #define ENTRY_PCD 0b000010000 | ||
| #define ENTRY_ACESSED 0b000100000 | ||
|
|
||
| // keep this flag should always be 0 | ||
| #define ENTRY_PS 0b010000000 | ||
|
|
||
| // only for page table entries, available for OS use in page directory | ||
| #define ENTRY_GLOBAL 0b100000000 | ||
| #define ENTRY_DIRTY 0b001000000 | ||
|
|
||
| // these bits are also available for the OS | ||
| #define ENTRY_AVL 0b111000000000 | ||
|
|
||
| // bits 12-31 | ||
| #define ENTRY_ADDR 0xfffff000 | ||
|
|
||
| // default entry is present, read/writable, and accessable by user and | ||
| // supervisor | ||
| #define DEFAULT_ENTRY_FLAGS (ENTRY_PRESENT | ENTRY_RW | ENTRY_US) | ||
|
|
||
| // note: these structures are lacking attrib packed | ||
| typedef struct { | ||
| PageDirectoryEntry entries[PAGE_ENTRY_COUNT]; | ||
| } PageDirectory; | ||
|
|
||
| // note, this structure must be 4KiB aligned | ||
| typedef struct { | ||
| PageTableEntry entries[PAGE_ENTRY_COUNT]; | ||
| } PageTable; | ||
|
|
||
| bool pageTablePresent(PageDirectoryEntry tableEntry); | ||
| bool pageEntryPresent(PageTableEntry entry); | ||
|
|
||
| // sets the entry's physical page to that of addr | ||
| void setEntryAddr(PageTableEntry *entry, const void *addr); | ||
|
|
||
| // NOTE: IF PageDirectory* IS NULL IT USES THE CURRENT DIRECTORY (unless | ||
| // otherwise specified) | ||
|
|
||
| // sets the active page directory, if NULL uses the identity directory | ||
| void setActivePageDir(PageDirectory *dir); | ||
|
|
||
| // gets the current page dir from cr3 | ||
| PageDirectory *getActivePageDir(void); | ||
|
|
||
| /* | ||
| * resets the translation lookaside buffer | ||
| * the TLB needs to be reset whenever an entry is modified | ||
| */ | ||
| void resetTLB(void); | ||
|
|
||
| // adds a table to a directory, TLB must be reset manually if directory is the | ||
| // current page directory | ||
| void addTableToDirectory(PageDirectory *directory, uint16_t idx, | ||
| PageTable *table, uint32_t flags); | ||
|
|
||
| // translation helpers | ||
| uint16_t vaddrDirectoryIdx(const void *vaddr); | ||
| uint16_t vaddrEntryIdx(const void *vaddr); | ||
| uint16_t vaddrOffset(const void *vaddr); | ||
|
|
||
| // translates table indexes and offset to virtual address | ||
| void *toVaddr(uint16_t dirIdx, uint16_t tableIdx, uint16_t offset); | ||
|
|
||
| // returns the associated directory entry of vaddr, never null | ||
| PageDirectoryEntry *vaddrDirEntry(PageDirectory *directory, const void *vaddr); | ||
|
|
||
| // returns the associated table entry of vaddr, null if invalid/unmapped address | ||
| PageTableEntry *vaddrTableEntry(PageDirectory *directory, const void *vaddr); | ||
|
|
||
| // identity maps the PageTable at directory index idx | ||
| void identityMapTable(PageDirectory *directory, uint16_t idx, uint32_t flags); | ||
|
|
||
| /* | ||
| * Converts virtual address to physical address | ||
| * (according to the current page table/directory) | ||
| * returns NULL when the address is invalid/unmapped | ||
| */ | ||
| void *vaddrToPaddr(PageDirectory *dir, const void *vaddr); | ||
|
|
||
| /* | ||
| * enables paging and identity maps the kernel (1st MiB) | ||
| * as well as identity mapping 1MiB - 12MiB | ||
| */ | ||
| void initPaging(void); | ||
|
|
||
| #endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| test_composition done | ||
| test_identity done | ||
| test_swap done | ||
| test_modify_in_place done | ||
| test_entry_not_present done |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.