Skip to content
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

Paging #29

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ OS_BIN := mOS.bin

C_FILES = $(shell find -name '*.[ch]')

OBJ_NAMES := src/os/main.o src/os/test.o os_entry.o src/lib/video/VGA_text.o \
OBJ_NAMES := src/os/main.o src/os/test.o os_entry.o src/os/paging.o \
src/os/hard/idt.o src/os/hard/except.o src/os/hard/pic.o \
src/lib/device/serial.o src/lib/container/ring_buffer.o \
src/lib/stdlib/stdio.o src/lib/stdlib/stdlib.o src/lib/stdlib/string.o \
src/lib/pit/pit.o
src/lib/pit/pit.o src/lib/video/VGA_text.o


.PHONY: clean qemu test
Expand Down
50 changes: 50 additions & 0 deletions docs/os/paging.md
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.

[Decomposition](vaddr_decomp.png)
Sploder12 marked this conversation as resolved.
Show resolved Hide resolved

### 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.

Sploder12 marked this conversation as resolved.
Show resolved Hide resolved
#### 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)
Binary file added docs/os/vaddr_decomp.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/os/main.c
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#include "device/serial.h"
#include "hard/idt.h"
#include "paging.h"
#include "pit/pit.h"
#include "test.h"
#include "video/VGA_text.h"

int os_main() {
makeInterruptTable();
initPaging();
init_pit();
serialInit();
clearScreen(black);
Expand Down
153 changes: 153 additions & 0 deletions src/os/paging.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#include "paging.h"

#include "stdlib/string.h"

/*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
Sploder12 marked this conversation as resolved.
Show resolved Hide resolved

// 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)

PageDirectory *idendirectory = (PageDirectory *)(ID_PAGE_DIRECTORY_BASE);

bool pageTablePresent(PageDirectoryEntry tableEntry) {
return tableEntry & ENTRY_PRESENT;
}

bool pageEntryPresent(PageTableEntry entry) { return entry & ENTRY_PRESENT; }
Sploder12 marked this conversation as resolved.
Show resolved Hide resolved

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 cr3, %0" : : "a"(dir));
}

PageDirectory *getActivePageDir(void) {
PageDirectory *dir = NULL;

__asm__ volatile("mov %0, cr3" : "=r"(dir));

return dir;
}

void resetTLB(void) { setActivePageDir(getActivePageDir()); }
Sploder12 marked this conversation as resolved.
Show resolved Hide resolved

#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) {
return ((uint32_t)(vaddr) >> PAGE_ENTRY_OFFSET) & 0b1111111111;
Sploder12 marked this conversation as resolved.
Show resolved Hide resolved
}

// low 12 bits
uint16_t vaddrOffset(const void *vaddr) { return (uint32_t)(vaddr)&0xfff; }
Sploder12 marked this conversation as resolved.
Show resolved Hide resolved

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 * 0x400000;
Sploder12 marked this conversation as resolved.
Show resolved Hide resolved

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 eax, cr0\n\t"
"or eax, 0x80000001\n\t"
"mov cr0, eax");
}
105 changes: 105 additions & 0 deletions src/os/paging.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#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;

#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
4 changes: 4 additions & 0 deletions tests/expected/os/paging.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
test_composition done
test_identity done
test_swap done
test_modify_in_place done
Loading