Skip to content
Snippets Groups Projects
Commit ddf7b3f4 authored by Florent Gluck's avatar Florent Gluck
Browse files

Added course/03-Memory_virtualization

Added lab2-paging
parent 942f190a
Branches master
No related tags found
No related merge requests found
Showing with 1590 additions and 0 deletions
---
author: Florent Gluck - Florent.Gluck@hesge.ch
title: Memory Virtualization
date: \vspace{.5cm} \footnotesize \today
pandoc-latex-fontsize:
- classes: [tiny]
size: tiny
- classes: [verysmall]
size: scriptsize
- classes: [small]
size: footnotesize
- classes: [huge, important]
size: huge
---
[//]: # ----------------------------------------------------------------
# Reminder
[//]: # ----------------------------------------------------------------
## Protection
- \textcolor{mygreen}{User processes must be \textbf{restricted} to}:
- only access the memory that "belongs" to them
- unprivileged CPU instructions
\vspace{.3cm}
- \textcolor{myred}{Only the operating system's kernel has \textbf{unrestricted} access to the entire CPU instruction set}:
- instructions to manipulate privilege levels
- instructions to control I/O
- instructions to configure memory access
- instructions to control interrupts
- etc.
[//]: # ----------------------------------------------------------------
## Privilege levels
To provide **protection**, at least two CPU modes are required:
1. \textcolor{myred}{\textbf{Privileged} mode} (also called "supervisor" or "kernel" mode)
- kernel runs in privileged mode $\rightarrow$ access to the entire CPU instruction set\
$\rightarrow$ the kernel can do **anything** it wants!
1. \textcolor{mygreen}{\textbf{User} mode}
- applications run in user mode $\rightarrow$ limited to a subset of the CPU instruction set\
$\rightarrow$ applications are **limited** in what they can do
- In special cases, like system calls or interrupts, the CPU **switches modes**
[//]: # ----------------------------------------------------------------
# Address space
[//]: # ----------------------------------------------------------------
## Address space
\definecolor{palechestnut}{rgb}{0.87, 0.68, 0.69}
\setlength{\fboxsep}{6pt}
\fcolorbox{black}{palechestnut!50}{\parbox{10cm}{\textbf{Address space} = range of addresses that can be \textbf{addressed} (or accessed) by the CPU}}
\vspace{.5cm}
Example of a 32 KB address space:
\vspace{.3cm}
![](images/basic_address_space.png){ width=70% }
[//]: # ----------------------------------------------------------------
## Process address space
\small
:::::: {.columns}
::: {.column width="50%"}
\vspace{1cm}
- The kernel creates an **abstraction** of physical memory $\rightarrow$ the process address space
\vspace{.3cm}
- **Process address space** = addresses that can be accessed by the process: code, stack, heap, static variables, constants, etc.
:::
::: {.column width="50%"}
**Process address space**
\vspace{.5cm}
\centering
![](images/process_address_space.png){ width=100% }
:::
::::::
[//]: # ----------------------------------------------------------------
## Process address space: example with 3 processes
\centering
![](images/multiple_processes_address_spaces.png){ width=100% }
[//]: # ----------------------------------------------------------------
## Physical address space
:::::: {.columns}
::: {.column width="50%"}
\vspace{1cm}
- Depending on the CPU architecture, the physical address space can be quite large, e.g. 1024 GB (1 TB) on Intel x86-64
\vspace{.5cm}
- Addresses can contain:
- RAM
- devices
- nothing
:::
::: {.column width="50%"}
\centering
![](images/address_space.png){ width=80% }
:::
::::::
[//]: # ----------------------------------------------------------------
## Memory virtualization
::: incremental
- The kernel creates an **abstraction** of physical memory for each process: the process address space
- How can the kernel build this abstraction of a **private**, potentially large address space for multiple processes on top of a single, physical memory?
- by **virtualizing** the memory!
- ... But how?
- by making the process think it's loaded at a particular address and it has access to the whole memory
- Reality is very different $\rightarrow$ the kernel **virtualizes** the addresses...
- and the process can only access addresses **allowed** by the kernel!
:::
<!--
[//]: # ----------------------------------------------------------------
## Multiple processes
\small
:::::: {.columns}
::: {.column width="60%"}
\vspace{1cm}
- Here, example with:
- \small 4GB virtual address space
- 3GB physical address space
\vspace{.3cm}
- The kernel must allow **multiple** processes to reside **concurrently** in memory
- A process **must not** be able to corrupt the kernel
- **\textcolor{myred}{Protection $\rightarrow$ critical issue!}**
:::
::: {.column width="40%"}
\centering
![](images/multiple_processes.png){ width=90% }
:::
::::::
[//]: # ----------------------------------------------------------------
## Process address space vs kernel address space
\small
:::::: {.columns}
::: {.column width="60%"}
\vspace{2cm}
- The kernel can access the **entire** virtual address space of the CPU
- On the other hand, a process can **only access** its **own** address space
- The process' address space is **created** by the kernel
:::
::: {.column width="40%"}
\centering
![](images/multiple_processes.png){ width=90% }
:::
::::::
[//]: # ----------------------------------------------------------------
## Every address you see is virtual
```{.verysmall .c}
#include <stdio.h>
#include <stdlib.h>
int tab[128*1024];
int main(int argc, char *argv[]) {
printf("@code : %p\n", main);
printf("@data : %p\n", tab);
printf("@heap : %p\n", malloc(512*1024));
int x = 3;
printf("@stack: %p\n", &x);
return 0;
}
```
```{.verysmall .sh}
$ ./address_space
@code : 0x565c920d
@data : 0x565cc040
@heap : 0xf7d2b010
@stack: 0xff9bd168
```
-->
[//]: # ----------------------------------------------------------------
# Address translation
[//]: # ----------------------------------------------------------------
## Fetching from memory
\centering
![](images/process_address_space_fetching.png){ width=100% }
[//]: # ----------------------------------------------------------------
## How to virtualize the memory?
- How to efficiently and flexibly virtualize the memory?
- How to provide flexibility needed by applications?
- How to control which locations a program can access?
- How to do these efficiently?
[//]: # ----------------------------------------------------------------
## Memory virtualization with the MMU
\small
- **MMU = Memory Management Unit**, hardware integrated into the CPU
- MMU handles the **mapping** of virtual addresses to physical addresses
- \footnotesize translates virtual addresses to physical addresses
- \textcolor{myred}{Memory addresses handled (read/write) by the CPU are always \textbf{virtual} addresses}
- Addresses actually seen by the memory hardware are **physical** addresses
- **\textcolor{myred}{Important}**: here, physical is unrelated to the amount of RAM installed on the system!
\vspace{.1cm}
\centering
![](images/mmu.png){ width=90% }
[//]: # ----------------------------------------------------------------
## Hardware-based address translation
- The MMU **transforms** each memory access (instruction fetch, load, store), from a **virtual** address into a **physical** address
- On **each** memory reference, an address **translation** is performed
- The kernel must **set up** the MMU and **manage** the memory:
- Keep track of which locations are free and used
- Set up access right (security)
- Allocate memory to processes
- Provide the mapping from virtual to physical addresses
[//]: # ----------------------------------------------------------------
## Virtual and physical address spaces
- CPUs featuring a MMU handle two kinds of address spaces:
- **virtual** address space
- **physical** address space
- \textcolor{myred}{Virtual address space $\geq$ physical}
- Examples:
- IA-32 (Intel \& AMD 32-bit):
- 32-bit virtual (4 GB), 32-bit physical (4 GB)
- x86-64/AMD64 (Intel/AMD 64-bit):
- 48-bit virtual (256 TB), 40-bit physical (1 TB)
<!--
[//]: # ----------------------------------------------------------------
# Relocation and segmentation
[//]: # ----------------------------------------------------------------
## Virtual space and relocation: example (1/2)
\small
:::::: {.columns}
::: {.column width="65%"}
- From the processes' point of view, its address space:
- \small starts at address 0
- \small can grow to a maximum of 16KB
- Memory references must be within [0,16KB]
- To virtualize the memory, kernel must place the process elsewhere than at address 0!
- How to **relocate** the process in memory so that it is **transparent** to the process?
- How to provide the **illusion** of a virtual address space starting at 0 when it starts at some other physical address?
:::
::: {.column width="35%"}
\centering
![](images/address_space_without_legends.png){ width=90% }
:::
::::::
[//]: # ----------------------------------------------------------------
## Virtual space and relocation: example (2/2)
\small
What the physical memory looks like when the process’s address space is **relocated** at address 32KB by the kernel:
\vfill
\centering
![](images/process_relocation.png){ width=75% }
[//]: # ----------------------------------------------------------------
## Dynamic relocation using base + limit registers
- Uses two registers:
- **base** register
- **limit** register
- The hardware provides special **privileged** instructions to modify base and limit registers, allowing the OS to change them when different processes are in execution
- Allow the address space to be placed anywhere in physical memory while
ensuring the process can only access its own address space
[//]: # ----------------------------------------------------------------
## Dynamic relocation mechanism
- Each program is written and compiled as if loaded at address 0
- When a program starts running, the kernel decides where in physical memory it should be loaded
- kernel sets the base register to that address
- In previous example, kernel loads process at physical address 32KB
- kernel sets base register to 32KB
- When any memory reference is generated by the process, it is translated
by the processor as:
- `physical address = virtual address + base`
[//]: # ----------------------------------------------------------------
## Protection with limit checking
- The **limit** register is a safe guard that prevents the process from refering memory address **outside** its address space!
- The MMU first checks that the memory reference is within bounds:
```
virtual address + base < limit?
```
- If not, the CPU raises an error, a *processor exception*
- For example, the Linux kernel generates the SEGV signal (segment violation or segmentation fault) that kills the offending process
[//]: # ----------------------------------------------------------------
## From dynamic relocation to segmentation
- Why called "**dynamic** relocation"?
- address relocation happens at **runtime**
- can move address spaces even **after** the process started running
- Dynamic relocation provides:
- address translation
- is efficient
- protection (through isolation)
- Dynamic relocation is implemented in Intel x86 CPUs
- called **segmentation**
- generalization of dynamic relocation
- provides base and limit per segment
[//]: # ----------------------------------------------------------------
## Segmentation example
\centering
![](images/segmentation.png){ width=100% }
[//]: # ----------------------------------------------------------------
## Problems with segmentation
Allocating variable-sized segments in memory leads to two major problems:
1. **External fragmentation**: because segments are variable-sized, free memory gets chopped up into odd-sized pieces, and thus satisfying a memory-allocation request is difficult
1. **Not flexible** enough to support **sparse** address spaces (address spaces featuring large amounts of unused space)
[//]: # ----------------------------------------------------------------
## External fragmentation
:::::: {.columns}
::: {.column width="50%"}
\small
- We want to allocate a contiguous chunk, smaller than the total available free space
\vspace{1cm}
- Enough scattered free space available
- However, not enough contiguous free space available to satisfy the allocation request
$\hspace{.8cm}$ $\rightarrow$ **external fragmentation!**
:::
::: {.column width="50%"}
\centering
![](images/external_fragmentation.png){ width=35% }
:::
::::::
[//]: # ----------------------------------------------------------------
## Introduction to paging
- Instead of splitting up a process's address space into some number of
variable-sized logical segments (e.g., code, heap, stack), we divide it into
fixed-sized units, each called a **page**
- Physical memory is viewed as an array of fixed-sized slots called **frames**
- each can contain a single virtual-memory page
- Question: how to virtualize memory with pages?
-->
[//]: # ----------------------------------------------------------------
# Paging
[//]: # ----------------------------------------------------------------
## What is paging?
- Paging is a technique used to perform address translation
- Almost every CPU today implements it in hardware
\vspace{.3cm}
- Paging concept:
- The space of all virtual addresses is called the **virtual address space**
- divided into blocks of equal size, called **pages**
- The space of all physical addresses is called the **physical address space**
- divided into blocks of equal size, called **frames**
- Pages and frames have the **same size** (typically 4KB)
<!--
- Paging solves the main issues that plague segmentation: **external fragmentation** and **flexibility** to support sparse address spaces
-->
[//]: # ----------------------------------------------------------------
## Paging on x86-64
![](images/address_translation_x86-64.png){ width=100% }
[//]: # ----------------------------------------------------------------
## Simple paging example
\footnotesize
- Example of a 256-byte virtual address space (16 pages) on a system with a 128-byte physical address space (8 frames)
- **\textcolor{myred}{Important}**: **each process, kernel included, has its own virtual to physical mapping**
\vfill
\centering
![](images/paging_simple_ex.png){ width=70% }
[//]: # ----------------------------------------------------------------
## Benefits of paging
- Flexibility
- Simplicity of free-space management
- For instance, to place the 64-byte (4 pages) process' virtual address space into an eight-frame physical address space, the kernel simply needs to find 4 free frames
[//]: # ----------------------------------------------------------------
## Page table
- A **page table** is used to record where each virtual page of the address space is placed in the physical address space
- The page table **\textcolor{myred}{maps}** **virtual pages** to **physical frames**
- \textcolor{myred}{The page table is a \textbf{per-process} data structure}, including the kernel's
- stores the physical address translation for each virtual page of the process' address space
- Page table of the previous example:
\scriptsize
**Page (virtual)** **Frame (physical)**
------------------ ----------------------
0 3
1 7
2 5
3 2
[//]: # ----------------------------------------------------------------
## Address translation via paging
- How to **translate** a virtual address into a physical one?
- In other words, how to obtain the physical address of a virtual address?
- The virtual address is **divided** into two parts:
- a **page number**
- an **offset** within the page
[//]: # ----------------------------------------------------------------
## Paging example (1/3)
- Let's consider the previous example and page table:
- 256-byte virtual address space, 128-byte physical address space
- 16-byte page size, 16 pages, 8 frames
- 8 bits are required to represent a vitual address (where `va7` is the most significant bit)
\centering
![](images/paging_virtual_address_ex_1.png){ width=50% }
- Because page size is 16 bytes, the virtual address can be divided as follow:
\centering
![](images/paging_virtual_address_ex_2.png){ width=50% }
[//]: # ----------------------------------------------------------------
## Paging example (2/3)
\footnotesize
- What is the physical address of virtual address 21 (00010101b)?
\centering
![](images/paging_virtual_address_ex_3.png){ width=45% }
- Page = 0001b = 1, offset = 0101b = 5
- The page table maps page 1 to frame 7, hence physical address is 01110101b = 117
[//]: # ----------------------------------------------------------------
## Paging example (3/3)
\centering
![](images/paging_virtual_address_ex_4.png){ width=100% }
[//]: # ----------------------------------------------------------------
## Paging: important remarks
- The **offset** in a given page **remains the same** in the frame
- The offset tells us which byte within the page or frame is being addressed
[//]: # ----------------------------------------------------------------
# Page table structure
[//]: # ----------------------------------------------------------------
## Page table content
\small
- The simplest page table structure is a *linear page table*
- \footnotesize simply an array of *page-table entries* (PTE)
- The page table is mainly used to retrieve the frame corresponding to a given page
- A PTE typically contains: PFN (physical frame number), P (present in RAM), R/W (r/w access), U/S (user or supervisor), etc.
- To retrieve the frame number (and associated data) which is stored in a PTE, simply retrieve the PTE
- To retrieve the PTE, simply lookup the page table at *page number* index in the page table
- Example of an Intel IA-32 PTE for a linear page table:
\centering ![](images/paging_PTE.png){ width=90% }
[//]: # ----------------------------------------------------------------
## Linear page table example
\centering
![](images/paging_linear_page_table.png){ width=100% }
[//]: # ----------------------------------------------------------------
## Linear page table location and size
- Linear page table structure stored **contiguously** in physical memory
- Let's consider a 32-bit virtual address space (4GB), 4KB pages, 4-byte PTE and 8MB physical memory
- 4KB page means 12-bit offset and 20-bit page number (1'048'576 pages)
- $2^{20} \cdot 4$ = 4MB of RAM required per page table!
- **\textcolor{myred}{Problems}**:
- page table size remains the same regardless of the desired mapping (small or large)
- potentially **large regions** of unused entries are **stored in memory**
- huge memory usage for nothing!
[//]: # ----------------------------------------------------------------
## How to reduce page table size? (1/2)
- Simple solution: **bigger pages**
- Let's consider a 32-bit virtual address space (4GB), 64KB pages, 4-byte PTE and 8MB physical memory
- 64KB page means 16-bit offset and 16-bit page number (65536 pages)
- $2^{16} \cdot 4$ = 256KB of RAM required per page table
- **\textcolor{myred}{Problems}**[^1]:
- big pages lead to waste within each page
- wasted space when pages are partially used
- memory can quickly fill up
$\hspace{1cm}$ $\rightarrow$ **\textcolor{myred}{internal fragmentation!}**
[^1]: \scriptsize Reason why most systems use small page sizes (4/8/16KB)
[//]: # ----------------------------------------------------------------
## How to reduce page table size? (2/2)
- How to reduce internal fragmentation?
- How to avoid storing all those unused (unmapped) regions in the page table instead of keeping them all in memory?
- **Idea**:
- keep small pages (~4KB), but breaks the page table into multiple tables
- very effective approach used in all modern CPUs
- called "multi-level paging"
[//]: # ----------------------------------------------------------------
## Multi-level paging
- Example: use a 2-level paging system $\rightarrow$ to page the page table!
- Divide the page table into page-sized chunks
- If an entire page of page-table entries (PTEs) is unused (unmapped) $\rightarrow$ don’t allocate that page of the page table
- To track whether a page of the page table is used (and if used, where it is in memory), use a new structure: a **page directory**
- The page directory is used to indicate:
- where a page of the page table is
- that the entire page of the page table contains no mapping (no used entries)
[//]: # ----------------------------------------------------------------
## 2-level paging with Intel IA-32
\small
- IA-32 offers a virtual address space of 32 bits ($2^{32}$ = 4GB), 4KB (4096 bytes) pages, 4-byte page directory entries and 4-byte page table entries
- A 32-bit virtual address is structured as follow:
\centering
![](images/paging_2level_page_table.png){ width=70% }
- `Directory` is an index in the page directory which identifies a page directory entry which indentifies where to find the inner page table in physical memory
- `Table` is an index in the page table which identifies an entry which identifies the frame in physical memory
- `Offset` is the offset within the 4KB frame
[//]: # ----------------------------------------------------------------
## Diagram of 2-level paging with Intel IA-32
\centering
![](images/multi_level_paging.png){ width=90% }
[//]: # ----------------------------------------------------------------
## Intel diagram of 2-level paging with IA-32
\small
- The Intel IA-32 architecture uses the 2-level page table below
- The CR3 register holds the address of the page directory
\vfill
\centering
![](images/paging_IA-32.png){ width=90% }
[//]: # ----------------------------------------------------------------
## Page Directory Entry on Intel IA-32
```{.c .tiny}
// Structure of a page directory entry (PDE). Structure size: 4 bytes.
// A page directory (PD) = array of page directory entries.
// A page table (PT) holds 1024 pages -> can address up to 4MB (4KB pages).
// With 1024 page tables -> can address 4GB of RAM.
typedef struct {
// The bit below is the least significant
u32 present : 1; // PT currently loaded in memory (for swapping)
u32 rw : 1; // read-write privileges
u32 user : 1; // user or supervisor privilege
u32 write_through : 1; // write-through caching for the associated PT
u32 cache_disable : 1; // caching of the associated PT
u32 accessed : 1; // PT has been read or written (set by cpu)
u32 reserved : 1; // reserved (0)
u32 page_sz : 1; // page size: 0 = 4KB, 1 = 4MB
u32 gp : 1; // global page (ignored)
u32 available : 3; // unused, freely available for kernel use
// Frame number of the PT this PDE points to.
// To get the PT's physical address, simply multiply this by page size
u32 pagetable_frame_number : 20;
// The 20 bits above are the most significant
} __attribute__((packed)) PDE_t;
```
[//]: # ----------------------------------------------------------------
## Page Table Entry on Intel IA-32
```{.c .tiny}
// Structure of a page table entry (PTE). Structure size: 4 bytes.
// A page table (PT) = array of page table entries.
// A page holds 2^12 bytes = 4096 bytes = 4KB.
typedef struct {
// The bit below is the least significant
u32 present : 1; // page currently loaded in memory (for swapping)
u32 rw : 1; // read-write privileges
u32 user : 1; // user or supervisor privilege
u32 write_through : 1; // write-through caching for the associated page
u32 cache_disable : 1; // caching of the associated page
u32 accessed : 1; // page has been read or written (set by cpu)
u32 dirty : 1; // page has been written to (set by cpu)
u32 pat : 1; // only used when using "Page Attribute Table" (PAT)
u32 gp : 1; // indicates a global page
u32 available : 3; // unused, freely available for kernel use
u32 frame_number : 20; // frame number the page points to
// The 20 bits above are the most significant
} __attribute__((packed)) PTE_t;
```
[//]: # ----------------------------------------------------------------
## Intel IA-32 address translation example (1/2)
\centering
![](images/paging_2level_page_table_compact.png){ width=50% }
\footnotesize
- What is the physical address of virtual address 23605284 (0000000101 1010000011 000000100100b)?
- Let `pagedir` be the page directory used by the current process and `PAGE_SIZE` the size of a page:
```{.tiny}
#define PAGE_SIZE 4096
PDE_t pagedir[1024] __attribute__((aligned(4096)));
```
1. Obtain the index of the page directory entry (PDE) this page belongs to:
```{.tiny .c}
pde_idx = 23605284 >> 22; // extract the 10 most significant bits
= 101b
= 5 // 5th entry in the page directory
```
2. Obtain the page directory entry (PDE) at index 5 in the page directory:
```{.tiny .c}
PDE_t *pde = &pagedir[pde_idx];
```
[//]: # ----------------------------------------------------------------
## Intel IA-32 address translation example (2/2)
\centering
![](images/paging_2level_page_table_compact.png){ width=50% }
\footnotesize
3. Obtain the index of the page table entry the address belongs to:
```{.tiny .c}
pte_idx = (23605284 >> 12) & 1023; // 1023 = 1111111111b
= 0001011010000011b & 1111111111b
= 1010000011b
= 643
```
4. Obtain the page table entry (PTE) at index 643 in the page table:
```{.tiny .c}
PTE_t *pagetable = (PTE_t *)(pde->pagetable_frame_number*PAGE_SIZE));
PTE_t *pte = &pagetable[643];
```
5. Obtain the corresponding frame and reconstruct the physical address:
```{.tiny .c}
uint32_t frame_number = pte->frame_number;
uint32_t phys_addr = (frame_number*PAGE_SIZE) | (23605284 & 4095);
```
- The physical address corresponding to the virtual address 23605284 is `phys_addr` above
[//]: # ----------------------------------------------------------------
## Intel IA-32 mapping example (1/2)
\footnotesize
**\textcolor{myred}{How to map}** the page located at virtual address 23605248 to the frame at physical address 16384?
\vspace{.3cm}
\centering
![](images/ia32_mapping_example.png){ width=100% }
[//]: # ----------------------------------------------------------------
## Intel IA-32 mapping example (2/2)
\footnotesize
\centering
![](images/paging_2level_page_table_compact.png){ width=50% }
::: incremental
- How to map the page located at virtual address 23605248 to the frame at physical address 16384?
1. Obtain the index of the page directory entry this page belongs to:
```{.tiny .c}
pde_idx = 23605248 >> 22; // extract the 10 most significant bits
```
1. Allocate a page table, if none already allocated for this page directory entry, then make the page directory entry point to the page table:
```{.tiny .c}
if (!page_dir[pde_idx].present) {
page_dir[pde_idx].present = 1;
page_table = allocate_new_page_table(); // alloc new frame
page_dir[pde_idx].pagetable_frame_number = page_table/FRAME_SIZE;
}
```
1. Map the page table entry (corresponding to the page) to the frame:
```{.tiny .c}
// Isolate the 10 bits of the page table index
pte_idx = (23605248 >> 12) & 1023;
page_table[pte_idx].frame_number = 16384/FRAME_SIZE;
```
:::
[//]: # ----------------------------------------------------------------
## Paging: quiz
\small
Consider a system featuring a 22-bit virtual address space and 20-bit physical address space. Virtual addresses are defined as:
\scriptsize
\begin{tabular}{|c|c|c|}
\hline
5-bit & 7-bit & 10-bit \\
\hline
Page Directory & Page Table & Offset \\
\hline
\end{tabular}
\footnotesize
a) What is the page size in bytes?
b) If one writes at virtual address 293'478, what corresponding page will be written to?
c) How many entries are in the page directory?
d) How many entries are in a page table?
e) What entry (index) in the page directory does virtual address 800'042 correspond to?
f) What entry (index) in the page table does virtual address 12'400 correspond to?
g) What is the offset value corresponding to virtual address 1'235'678?
<!--
a) page size = 2^10 bytes = 1024 bytes
b) 293478 / 1024 = 286.5996094 = 286
c) #entries in page table = 2^7 = 128
d) #entries in page directory = 2^5 = 32
e) 800042 >> 17 = 6
f) (123400 >> 10) & 127 = 120
g) 12345678 & 1023 = 334
-->
[//]: # ----------------------------------------------------------------
# Free space management
[//]: # ----------------------------------------------------------------
## Why free space management?
- Use of static variables is very limiting
::: incremental
- Need to be able to **dynamically** allocate memory
- How to **dynamically** manage the available RAM?
:::
[//]: # ----------------------------------------------------------------
## Basic free space management
:::::: {.columns}
::: {.column width="60%"}
- Divide the physical address space into frames
- **Keep track** of free and allocated frames
- Provide a way to:
- **allocate** a frame
- **free** a frame
\vspace{.3cm}
- Even better:
- allow allocation of blocks of arbitrary size
- size rounded up to a frame size
- allow a block to be freed
:::
::: {.column width="40%"}
\centering
![](images/frame_management.png){ width=100% }
:::
::::::
[//]: # ----------------------------------------------------------------
## Frame management
- How to keep track of free frames?
- Simplest method:
- array of bits
- each bit corresponds to a frame:
- bit == `0` : frame is free (available)
- bit == `1` : frame is used (allocated)
\vspace{.5cm}
\centering
![](images/frame_management_bitarray.png){ width=80% }
[//]: # ----------------------------------------------------------------
## Limitations and improvements
- Can only allocate "large" chunks of fixed-sized memory (the frame size)
- **Significant internal fragmentation!**
- might lead to large amount of wasted memory
- Solution:
- implement a layer on top of this simple mechanism
- allow for byte-granularity allocation
- However:
- many allocation strategies
- much more complex
[//]: # ----------------------------------------------------------------
# Use of paging in an OS
[//]: # ----------------------------------------------------------------
## Protection mechanism
- Reading from or writing to a page that has no mapping triggers a CPU exception (a fault)
- Paging can be used to detect memory addressing errors/bugs
- Examples:
- null or invalid pointers
- memory overflow, e.g. kernel stack overflow
[//]: # ----------------------------------------------------------------
## Detecting null pointers
\centering
![](images/paging_null_ptr.png){ height=90% }
[//]: # ----------------------------------------------------------------
## Detecting kernel stack overflow
\centering
![](images/paging_stack_overflow.png){ height=90% }
[//]: # ----------------------------------------------------------------
## Shared pages
- Paging makes it very easy to share blocks of memory, by simply duplicating page numbers in multiple frames
- This may be done with either code or data
- Shared memory and shared code (e.g. shared libraries) can be implemented in this fashion
[//]: # ----------------------------------------------------------------
## Shared pages example
\small
Three different users are running an editor simultaneously, but the code is only **present once** in memory (frames 3, 4, 6):
\vfill
\centering
![](images/paging_shared_pages.png){ width=100% }
[//]: # ----------------------------------------------------------------
## Multiple processes
- The kernel allows creating multiple processes that reside in physical memory concurrently
- Thanks to paging, the kernel can **ensure the isolation of processes** by creating a **separate** address space for each one:
- isolation of processes from each other
- isolation of processes from the kernel
[//]: # ----------------------------------------------------------------
## Address space: process vs kernel
:::::: {.columns}
::: {.column width="62%"}
\vspace{1cm}
\small
- The kernel's page table is configured so that it can access the entire virtual address space of the CPU
- For security reasons, the address space (virtual) of the process is intentionally **limited**
- The virtual address space of the process is created by the kernel
- It is the kernel that decides which pages of the process correspond to which frames in physical memory
:::
::: {.column width="38%"}
\centering
![](images/multiple_processes.png){ width=100% }
:::
::::::
[//]: # ----------------------------------------------------------------
## Real life memory organization
\centering
![](images/processes_and_phys_address_space.png){ width=100% }
[//]: # ----------------------------------------------------------------
## Identity mapping
- An **identity mapping** is when virtual addresses are mapped to the same physical addresses:
- virtual address X is mapped to physical address X
- It is as if there was no mapping
- Kernel can use it to simplify its memory management
\vfill
\centering
![](images/identity_mapping.png){ width=80% }
[//]: # ----------------------------------------------------------------
## Limited amount of RAM
A problem arises when physical memory (RAM) becomes saturated
- \textcolor{myred}{No more available frames!}
What to do? ...
[//]: # ----------------------------------------------------------------
## Virtual memory
- The MMU allows storing the content of a frame on disk
- this allows freeing up a frame in physical memory
- When the number of available frames becomes low, the kernel copies the content of the "oldest" frames to disk
- typically in a **swap** file
- the corresponding pages are marked as *\textcolor{myorange}{not present}* (in RAM)
- When the *\textcolor{myorange}{not present}* pages are accessed again, the kernel transfers their content from disk to some free frames
- this process is extremely slow, hence the massive degradation of performance when RAM becomes saturated
\vspace{.3cm}
\centering
\definecolor{palechestnut}{rgb}{0.87, 0.68, 0.69}
\setlength{\fboxsep}{6pt}
\fcolorbox{black}{palechestnut!50}{This mecanism is called \textbf{virtual memory}}
<!--
[//]: # ----------------------------------------------------------------
## TODO
- Combine slide "Intel IA-32 mapping example" with illustration from slide "Multi-level paging example: Intel IA-32"
- Every step is explained with a visual illustration
- new slide about page fault:
- https://en.wikipedia.org/wiki/Page_fault
- Section "Page faults" here: https://en.wikipedia.org/wiki/Memory_paging
-->
[//]: # ----------------------------------------------------------------
## Paging: summary
In summary, paging allows:
- Creating an isolated address space for each process and the kernel
- enforces security and controls memory access
- Using more memory than the available physical memory (RAM)
- thanks to using the disk as virtual memory
- Creating areas of contiguous virtual memory from fragmented physical memory (frames)
- Creating areas of shared memory between processes
[//]: # ----------------------------------------------------------------
## Resources
\small
- Operating Systems: Three Easy Pieces, Remzi H. and Andrea C. Arpaci-Dusseau. Arpaci-Dusseau Books\
\footnotesize [\textcolor{myblue}{http://pages.cs.wisc.edu/~remzi/OSTEP/}](http://pages.cs.wisc.edu/~remzi/OSTEP/)
\small
- Operating System Concepts, 9th edition, A. Silberschatz, G. Gagne, P. B. Galvin\
\footnotesize [\textcolor{myblue}{https://archive.org/details/operating-system-concepts-9th-edition}](https://archive.org/details/operating-system-concepts-9th-edition)
\small
- Intel 64 and IA-32 Architectures Software Developer's Manual Vol3: System Programming Guide\
\footnotesize Available in `resources/Intel_Manuals` directory on the git
<!--
[//]: # ----------------------------------------------------------------
## Memory access
- The CPU can **only access** its registers and main memory (RAM)
- it cannot, for example, directly access a disk: any data stored on disk must first be transferred (slow) into main memory before the CPU can access it
- Accessing main memory is **much slower** than accessing registers
- memory caches tremendously improve performance, but are limited in size
[//]: # ----------------------------------------------------------------
## Early systems
:::::: {.columns}
::: {.column width="70%"}
Operating systems of the early days:
- OS = set of routines (library) in memory
- 1 running program (process) that uses the rest of the memory
:::
::: {.column width="30%"}
\centering
![](images/early_days.png){ width=90% }
:::
::::::
[//]: # ----------------------------------------------------------------
## Multiprogramming and time sharing
:::::: {.columns}
::: {.column width="70%"}
- Often called “batch computing”
- Multiple processes ready to run at a given time
- OS switches between them, e.g. when process performs I/O
- \textcolor{myred}{Long waits $\rightarrow$ poor interactivity}
- \textcolor{myred}{Processes only use a subset of the whole memory}
:::
::: {.column width="30%"}
\centering
![](images/multiprogramming.png){ width=90% }
:::
::::::
-->
\ No newline at end of file
File added
course/images/paging_null_ptr.png

138 KiB

course/images/paging_stack_overflow.png

143 KiB

include ../lab.mk
---
title : Programmation Système Avancée
author :
- "Professeur : Florent Gluck"
date : \today
papersize : A4
geometry : "left=2.5cm,right=2cm,top=1.5cm,bottom=2cm"
colorlinks : urlcolor
fontsize : 11pt
lang : fr-CH
---
# Lab2 - Gestion mémoire grâce à la pagination
## Introduction
Ce travail pratique se focalise sur une partie extrêmement importante de tout système d'exploitation, à savoir la gestion de la mémoire physique, la gestion de la mémoire virtuelle, ainsi que le mapping de mémoire virtuelle à physique.
## Objectifs
Les objectifs à réaliser dans le cadre de ce travail pratique sont décrits dans les sous-sections qui suivent.
### Objectif 1 : gestion de la mémoire physique
Dans un premier lieu, il s'agit de gérer la mémoire physique, c'est à dire l'allocation et la désallocation de frames. Pour cela, on désire implémenter une petite API permettant de gérer l'allocation et la désallocation de frames. Il s'agit donc d'un mécanisme d'allocation/désallocation dynamique de la mémoire, à l'image de `malloc` mais en bien plus primitif, car seul une allocation de 4KB est possible (taille d'une frame). Autrement dit, nous désirons avoir la possibilité d'allouer dynamiquement des *blocs* individuels de mémoire de la taille d'une frame.
Voici les fonctions à réaliser\ :
```{.c}
void frame_init(uint_t RAM_in_KB)
```
- initialise le système de frames avec la quantité de mémoire physique spécifiée et initialise correctement le tableau de bits indiquant l'état des frames du système (cf. plus bas)
```{.c}
static void frame_mark_used(int frame)
```
- marque une frame comme allouée
```{.c}
static void frame_mark_free(int frame)
```
- marque une frame comme disponible (libre)
```{.c}
void *frame_alloc()
```
- alloue une frame et renvoie son adresse physique (alignée à 4KB)
- la valeur `0xFFFFFFFF` doit être retournée si il n'y a plus de frame disponible
- le contenu de la frame retournée doit toujours être initialisé à 0
```{.c}
void frame_free(void *frame_addr)
```
- libère une frame
```{.c}
uint_t frame_total_free()
```
- renvoie le nombre de frames disponibles (utile pour obtenir la quantité de RAM disponible)
La fonction d'initialisation de frames, `frame_init`, doit marquer toutes les frames en RAM comme disponibles, sauf la région contigüe suivante\ :
- la zone mémoire de l'adresse physique 0 jusqu'à la dernière frame utilisée par le dernier module multiboot (d'où l'intérêt d'obtenir la dernière adresse mémoire utilisée par les modules multiboot, cf. labo précédent)
- pour rappel GRUB charge les modules à des adresses (en mémoire physique) situées à la suite du noyau.
En effet, les régions de mémoire physique déjà utilisées ne doivent pas être utilisées pour y allouer des frames\ ! Le noyau et les modules multiboot sont des zones de RAM déjà utilisées, donc à ne pas toucher et à marquer comme tel.
La fonction `frame_init` doit également marquer toutes les frames se trouvant en dehors de la RAM comme non disponibles. En effet, ces zones ne doivent pas être allouables car elles ne contiennent pas de RAM.
A noter que la plage d'adresses couvrant le framebuffer est une zone mémoire de type "périphérique" (I/O), donc inutilisable pour y allouer des frames.
### Objectif 2 : gestion de la mémoire paginée
Grâce à la gestion des frames, il est possible d'allouer ou libérer dynamiquement des frames, c'est à dire des blocs de mémoire physique de 4KB.
On désire maintenant avoir la possibilité de mapper une zone de mémoire virtuelle contigüe vers une zone de mémoire physique également contigüe. Par soucis de simplicité, on part du principe que la zone à mapper est alignée à une frontière de page et que sa taille est également un multiple de page.
Les fonctions suivantes sont à réaliser\ :
```{.c}
void paging_init(uint_t RAM_in_KB)
```
- réalise un mapping identité de toute la mémoire physique (RAM) disponible (à partir de l'adresse 0) afin que celle-ci soit adressable
- réalise un mapping identité du framebuffer VBE
- réalise un deuxième mapping de la taille du framebuffer VBE, mais à partir de l'adresses virtuelle 3GB (à une frontière de page) vers l'adresse physique du framebuffer VBE
- en d'autres termes, on désire pouvoir adresser le framebuffer à deux adresses différentes\ : l'adresse "originelle retournée dans la structure `multiboot_t`" tel qu'initalisé par GRUB et l'adresse virtuelle ci-dessus à 3GB
- supprime, via la fonction `unmap_page`, tout mapping pour la page 0 (*guard page*)
- supprime, via la fonction `unmap_page`, tout mapping pour la première page de la stack noyau (*guard page*)\ ; attention à ce que la section `.stack` dans `kernel.ld` soit alignée à une page
```{.c}
static PTE_t *mmap_page(PDE_t *pagedir, uint32_t virt_addr, uint32_t phys_addr, enum privilege_t privilege, enum access_t access)
```
- mappe, dans le répertoire de pages spécifié, avec les droits et accès spécifiés, une page (adresse virtuelle) dans une frame (adresse physique)
- fonction privée
- alloue dynamiquement la mémoire nécessaire pour stocker la table de pages, si celle-ci n'a pas déjà été allouée
- si une table de pages a été allouée, renvoie son adresse
- renvoie 0 dans le cas contraire
- au niveau des structures de données, voici comment initialiser leurs champs\ :
- répertoire de pages\ :
- `present` à initialiser à 1
- `rw` à initialiser à `access`
- `user` à initialiser à `privilege`
- `page_sz` à initialiser à `0`
- `pagetable_frame_number` à initialiser avec le numéro de frame où se trouve la table de page correspondante
- tous les autres champs doivent être initialisés à `0`
- table de pages\ :
- `present` à initialiser à 1
- `rw` à initialiser à `access`
- `user` à initialiser à `privilege`
- `frame_number` à initialiser avec le numéro de frame de destination
- tous les autres champs doivent être initialisés à `0`
```{.c}
void paging_mmap(PDE_t *pagedir, uint32_t virt_addr, uint32_t phys_addr, uint32_t size, enum privilege_t privilege, enum access_t access)
```
- similaire à `mmap_page`, mais plutôt que de mapper une page, la fonction mappe une région de pages contigües dans une région de frames contigües
- fonction publique
- l'adresse virtuelle et l'adresse physique doivent être alignées à une frontière de page
- dans le cas où la taille spécifiée n'est pas un multiple de taille de page, la région mappée est arrondie à la taille de page supérieure (p.ex: taille de 4200 bytes -> mapping de deux pages, soit 8192 bytes)
```{.c}
static void unmap_page(PDE_t *pagedir, uint32_t virt_addr)
```
- supprime le mapping de la page à l'adresse spécifiée
- fonction privée
### Objectif 3 : comportement du noyau
Au moment de son exécution, votre noyau doit réaliser les opérations suivantes (pas forcément dans l'ordre listé ici)\ :
- initialiser la mémoire paginée
- tester et valider que le framebuffer accédé à l'adresse virtuelle 3GB fonctionne correctement
- tester et valider que vos guard pages sont correctes (cf. consignes pour `paging_init` plus haut)
- adresser la page 0 devrait provoquer un reboot du système
- adresser la première page de la stack du noyau devrait avoir également provoquer un reboot du système
- une fois validé, ce code est à commenter bien sûre
- nous verrons dans la suite du cours pourquoi ceci provoque un reboot du système
## Code source fourni
Afin de vous aider dans votre tâche, quelques nouveaux fichiers sources vous sont fourni dans le répertoire `skeleton/yoctos` de ce travail pratique\ :
\footnotesize
```
kernel
`-- mem
|-- frame.c
|-- frame.h
|-- paging.c
|-- paging.h
`-- paging_asm.s
```
\normalsize
Une partie de la gestion mémoire est la mise en place d'une table de segments et la programmation des registres de segments permettant d'adresser tout l'espace d'adressage du processeur, soit $2^{32}$ bits (4GB) pour l'architecture IA-32. Le code fourni réalisant ces opérations se trouve dans les fichiers `gdt.h`, `gdt.c` et `gdt_asm.s`. Bien que nécessaire au bon fonctionnement du noyau, cette partie n'est pas décrite dans le cours ou ici, car ceci est particulier à l'architecture Intel x86 et la gestion mémoire via la pagination est plus universel, actuel et flexible (mais au coût d'une complexité plus élevée).
Des macros liées à la gestion des frame vous sont fournies dans le fichier `frame.h`\ :
Objet Description
-------------------------------- ---------------------------------------------------------------
\footnotesize `FRAME_SIZE` taille d'une frame en bytes
\footnotesize `ADDR_TO_FRAME_NB` converti une adresse physique en un numéro de frame
\footnotesize `FRAME_NB_TO_ADDR` converti un numéro de frame en une adresse physique
\footnotesize `FRAME_COUNT` renvoie le nombre de frames nécessaires pour y stocker le nombre de bytes spécifiés
------------------------------------------------------------------------------------------------
Les structures de données liées à la pagination vous sont fournies dans le fichier `paging.h`. En particulier, vous pouvez y trouver les macros et structures ci-dessous\ :
Objet Description
-------------------------------- ---------------------------------------------------------------
\footnotesize `PAGE_SIZE` taille d'une page ou d'une frame en bytes
\footnotesize `PAGES_IN_PT` nombre de pages dans une table de pages
\footnotesize `PAGETABLES_IN_PD` nombre de tables de pages dans un répertoire de pages
\footnotesize `ADDR_TO_PDE` renvoie l'indice d'une PDE (entrée de répertoire de page) pour une adresse
virtuelle donnée
\footnotesize `ADDR_TO_PAGE_NB` converti une adresse virtuelle en un numéro de page
\footnotesize `PAGE_NB_TO_ADDR` converti un numéro de page en une adresse physique
\footnotesize `PAGE_COUNT` renvoie le nombre de pages nécessaires pour y stocker le nombre de bytes spécifiés
\footnotesize `enum privilege_t` type de privilège : `PRIVILEGE_USER` (*user*) ou `PRIVILEGE_KERNEL` (*supervisor*) ;
à utiliser dans le champs `user` des structures `pagetable_t` et `page_t`
\footnotesize `enum access_t` type d'accès : `ACCESS_READONLY` ou `ACCESS_READWRITE` ; à utiliser dans le
champs `rw` des structures `pagetable_t` et `page_t`
\footnotesize `PDE_t` structure représentant une entrée de répertoire de pages (*page directory entry*)
\footnotesize `PTE_t` structure représentant une entrée de table de pages (*page table entry*)
------------------------------------------------------------------------------------------------
Le fichier source `paging.c` contient la variable globale `kernel_pagedir` qui est le répertoire de pages du noyau et les fonctions à implémenter (vous pouvez en rajouter si besoin). Le répertoire de pages est un tableau de 1024 entrées de répertoire de pages (`PDE_t`). C'est là qu'il faudra faire pointer les entrées utilisées (i.e. les adresses virtuelles qui auront des mappings vers des frames physiques) vers une ou plusieurs tables de pages dont les entrées (de type `PTE_t`) pointeront vers des frames physiques.
De plus, des fonctions essentielles et nécessitant l'utilisation d'instructions assembleur non disponibles en langage C vous sont fournies dans le fichier `paging_asm.s`. Leurs prototypes se trouvent dans `paging.h` et sont présentés ci-dessous\ :
Fonction Description
------------------------------------------------------ --------------------------------------------------------------
\scriptsize `void paging_load_pagedir(PDE_t *pagedir)` charge le répertoire de pages spécifié dans le CPU et le rend
actif (càd charge le registre `CR3` avec l'adresse de celui-ci)
\scriptsize `PDE_t *paging_get_current_pagedir()` renvoie le contenu du répertoire de pages courant (càd renvoie
la valeur du registre `CR3`)
\scriptsize `void paging_enable()` active la pagination ;
**\textcolor{red}{attention}** : assurez-vous absolument d'avoir chargé au
préalable un répertoire de pages valide !
--------------------------------------------------------------------------------------------------------------------
## Remarques importantes
Il est important que le code lié à la gestion mémoire soit fiable et exempt de bugs car l'OS entier se base dessus. Le code à écrire est court (~200 lignes), mais rien ne doit être laissé au hasard. Je vous encourage donc à développer des tests (au moins pour le code de gestion de frames) afin de valider le bon fonctionnement de votre code.
Aussi, tout comme pour le labo précédent :
- Pensez à placer vos fichiers sources à un endroit dans la hiérarchie de fichiers **qui fait du sens**. Vous êtes libres de modifier la structure existante du projet et de rajouter/modifier/supprimer des répertoires.
## Echéance
Ce travail pratique est à terminer pour le 31 octobre.
File added
#include "frame.h"
// IA-32 can handle an address space of 4GB.
// 4GB = 1048576 pages (4KB each).
// If we represent each frame with 1 bit, we need 1048576 bits = 128KB (131072 bytes).
static uint8_t frame_bitmap[131072];
// Marks a frame as allocated.
// frame 0 represents physical addresses [0,4095]
// frame 33 represents physical addresses [33*4096,33*4096+4095] = [135168,139263]
static void frame_mark_used(int frame) {
// TODO
}
// Marks a frame as free/available.
static void frame_mark_free(int frame) {
// TODO
}
static bool is_frame_free(int frame) {
// TODO
}
void *frame_alloc() {
// TODO
}
void frame_free(void *frame_addr) {
// TODO
}
uint_t frame_total_free() {
// TODO
}
void frame_init(uint_t RAM_in_KB) {
// TODO
}
#ifndef _FRAME_H_
#define _FRAME_H_
#include "common/types.h"
// The hardware supports 3 frame sizes: 4KB, 4MB and 2MB (when PAE is enabled)
// Our kernel only uses 4KB frames.
#define FRAME_SIZE 4096
// Converts a 32-bit address into a frame number
#define ADDR_TO_FRAME_NB(addr) (((uint32_t)addr)/FRAME_SIZE)
// Converts a frame number into a 32-bit address
#define FRAME_NB_TO_ADDR(n) (((uint32_t)n)*FRAME_SIZE)
// Returns the number of frames required to store the given number of bytes
#define FRAME_COUNT(size) ((size + FRAME_SIZE - 1)/FRAME_SIZE)
// Initializes the physical frame subsystem, using the specified amount of physical memory.
extern void frame_init(uint_t RAM_in_KB);
// Allocates a frame (4KB) and returns its physical address.
// Returns the physical address of the frame or
// 0xFFFFFFFF if no more frames are available.
// REMARKS:
// The physical address is always aligned to a 4KB boundary.
// The frame's content is always zeroed.
extern void *frame_alloc();
// Free a frame.
// REMARK: doesn't check whether the frame was previously allocated or not.
extern void frame_free(void *frame_addr);
// Returns the total number of free frames.
// This can typically be used by a syscall to retrieve the amount of free RAM.
extern uint_t frame_total_free();
#endif
\ No newline at end of file
#include "paging.h"
#include "frame.h"
#include "x86.h"
static PDE_t kernel_pagedir[PAGETABLES_IN_PD] __attribute__((aligned(PAGE_SIZE)));
// Map, in the specified page directory, the page starting at virt_addr into
// the physical address (frame) phys_addr.
// This function dynamically allocates the necessary memory (frames) to store the
// page table (if not previously allocated).
// Return the new allocated page table if one was just allocated, otherwise returns 0.
static PTE_t *mmap_page(PDE_t *pagedir, uint32_t virt_addr, uint32_t phys_addr, enum privilege_t privilege, enum access_t access) {
// TODO
}
void paging_mmap(PDE_t *pagedir, uint32_t virt_addr, uint32_t phys_addr, uint32_t size, enum privilege_t privilege, enum access_t access) {
// TODO
}
static void unmap_page(PDE_t *pagedir, uint32_t virt_addr) {
// TODO
}
void paging_init(uint_t RAM_in_KB) {
// TODO
paging_load_pagedir(kernel_pagedir);
paging_enable();
}
#ifndef _PAGING_H_
#define _PAGING_H_
#include "common/types.h"
// The hardware supports 3 page sizes: 4KB, 4MB and 2MB (when PAE is enabled)
// Our kernel only uses 4KB pages.
#define PAGE_SIZE 4096
// The maximum number of pages in total on a IA-32 architecture is 2^20 (= 1048576 pages).
// 1048576 * 4096KB = 4GB
// Number of pages in a page table
#define PAGES_IN_PT 1024
// Number of page tables in a page directory
// (number of page directory entries in a page directory)
#define PAGETABLES_IN_PD 1024
// Returns the index of the PDE (page directory entry) for a given virtual address
#define ADDR_TO_PDE(addr) (((uint32_t)addr) >> 22)
// Converts a 32-bit address into a page number
#define ADDR_TO_PAGE_NB(addr) (((uint32_t)addr)/PAGE_SIZE)
// Converts a page number into a 32-bit address
#define PAGE_NB_TO_ADDR(n) (((uint32_t)n)*PAGE_SIZE)
// Returns the number of pages required to store the given number of bytes
#define PAGE_COUNT(size) ((size + PAGE_SIZE - 1)/PAGE_SIZE)
// For details about a page directories and a page tables, see Vol. 3A System Programming Guide Part 1
// of the Intel 64 and IA-32 Architecture Software Dev's Manual.
// In particular, section 3.7.6 Page-Directory and Page-Table Entries
enum privilege_t {
PRIVILEGE_KERNEL = 0,
PRIVILEGE_USER = 1
};
enum access_t {
ACCESS_READONLY = 0,
ACCESS_READWRITE = 1
};
// Structure of a page directory entry (PDE). A page directory is an array of page directory
// entries.
// A page table can hold 1024 pages which means addressing up to 4MB of RAM (using 4KB pages).
// Using 1024 page tables, we can address 4GB of RAM.
typedef struct {
// The bit below is the least significant
uint32_t present : 1; // page table currently loaded in memory (used for swapping)
uint32_t rw : 1; // specifies read-write privileges
uint32_t user : 1; // specifies user or supervisor privilege
uint32_t write_through : 1; // enable/disable write-through caching for the associated
// page table
uint32_t cache_disable : 1; // enable/disable caching of the associated page table
// (useful for MMIO)
uint32_t accessed : 1; // page table has been accessed by read or write (set by cpu)
uint32_t reserved : 1; // reserved (0)
uint32_t page_sz : 1; // page size: 0 = 4KB, 1 = 4MB
uint32_t gp : 1; // indicates a global page (ignored)
uint32_t available : 3; // unused, freely available for kernel use
// Frame number of the PT this PDE points to.
// To get the PT's physical address, simply multiply this by page size
uint32_t pagetable_frame_number : 20;
// The 20 bits above are the most significant
} __attribute__((packed)) PDE_t;
// Structure of a page table entry (PTE). A page table is an array of page table entries.
// A page can hold 2^12 bytes = 4096 bytes = 4KB.
typedef struct {
// The bit below is the least significant
uint32_t present : 1; // page currently loaded in memory (used for swapping)
uint32_t rw : 1; // specifies read-write privileges
uint32_t user : 1; // specifies user or supervisor privilege
uint32_t write_through : 1; // enable/disable write-through caching for the associated page
uint32_t cache_disable : 1; // enable/disable caching of the associated page
// (useful for MMIO)
uint32_t accessed : 1; // page has been accessed by read or write (set by cpu)
uint32_t dirty : 1; // page has been written to (set by cpu)
uint32_t pat : 1; // only used when using "Page Attribute Table" (PAT),
// 0 otherwise
uint32_t gp : 1; // indicates a global page
uint32_t available : 3; // unused, freely available for kernel use
uint32_t frame_number : 20; // frame number the page points to
// The 20 bits above are the most significant
} __attribute__((packed)) PTE_t;
// Setup the kernel page directory with the following two mappings:
// - Identity map the available RAM so that the kernel can access it as if there was no paging.
// - Identity map the VBE framebuffer.
// Then, loads the page directory and activate paging.
void paging_init(uint_t RAM_in_KB);
// Maps, in the specified page directory, size bytes starting at virtual address virt_addr
// into physical address phys_addr. Both virtual and physical areas are contiguous.
// This function dynamically allocates the necessary frames to store the page tables.
// IMPORTANT: virt_addr and phys_addr must be aligned to a page size (4KB).
void paging_mmap(PDE_t *pagedir, uint32_t virt_addr, uint32_t phys_addr, uint32_t size, enum privilege_t privilege, enum access_t access);
// Enable paging.
// Assembly function implemented in paging_asm.s
void paging_enable();
// Load the specified page directory into the CPU and make it active.
// Assembly function implemented in paging_asm.s
void paging_load_pagedir(PDE_t *pagedir);
// Return the page directory currently used by the CPU.
// Assembly function implemented in paging_asm.s
PDE_t *paging_get_current_pagedir();
#endif
%include "const.inc"
global paging_enable
global paging_load_pagedir
global paging_get_current_pagedir
section .text: ; start of the text (code) section
align 4 ; the code must be 4 byte aligned
; Enable paging.
; Paging is enabled by setting bit 31 (PG) of the CR0 register (CR0 = CR0 | 0x80000000)
; void paging_enable(void)
paging_enable:
mov eax,cr0
or eax,0x80000000
mov cr0,eax
ret
; Load the CR3 register with the specified page directory.
; void paging_load_pagedir(uint32_t dir_entry_addr)
paging_load_pagedir:
mov eax,[esp+4]
mov cr3,eax
ret
; Return the page directory loaded into the CR3 register.
; pagetable_t *paging_get_current_pagedir();
paging_get_current_pagedir:
mov eax,cr3
ret
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment