Update on chapter 3 notes.

This commit is contained in:
Philippe Pittoli 2025-02-14 08:05:01 +01:00
parent c8b60efd77
commit 4fdf4028a4

View file

@ -87,7 +87,7 @@ XV6 kernel page table:
to prevent memory corruption from stack overflows
Virtual memory functions:
- walk: find PTE for a virt@
- walk: find PTE for a virt@ (can allocate a PTE in a page table)
- mappages: install PTEs for new mappings
- kvm_* = kernel virtual memory functions (kernel page table)
- uvm_* = same but for a user process
@ -99,18 +99,73 @@ Virtual memory functions:
- kvmmake creates a direct-map page table for the kernel
1. create the root kernel page table with a call to `kalloc`
(kalloc provides a pointer to a page, which is the type `pagetable_t`)
2. call kvmmap multiple times to set a few direct-map pages
2. call kvmmap (an overlay of `mappages` to handle errors) multiple times to set a few direct-map pages
kvmmap adds mapping to the kernel page table (when booting only), doesn't flush TLB or enable paging
mapped stuff:
uart registers, virtio mmio disk interface, PLIC, kernel text and data
and trampoline (for trap entry/exit) is mapped to the highest virtual address in the kernel
3. proc_mapstacks
3. proc_mapstacks allocates a kernel stack for each process in the `proc` (static) array of processes in `proc.c`
each kernel stack page is placed under the TRAMPOLINE kernel page with a following guard page (invalid page)
side note: the function is complex for no reason, it uses pointer arithmetics just to get an index (0-7)
TRAMPOLINE is a macro to get the phy@ of the trampoline page (MAXVA - PGSIZE)
KSTACK(p) is a macro to place a kernel page bellow the trampoline page with a guard page
KSTACK(p) = (TRAMPOLINE - ((p)+1)* 2*PGSIZE)
TRAMPOLINE & KSTACK are macros in memlayout.h and are use in proc_mapstacks
- main calls kvminithart which sets satp to the kernel root page table so the CPU can start using it
- each CPU caches PTEs in a Translation Look-aside Buffer (TLB)
=> when xv6 changes a page table it must tell the CPU to invalidate cached entry in the TLB
=> RISC-V has an `sfence.vma` instruction to flush the current CPU's TLB
=> `kvminithart` uses it after initializing sapt
=> `sfence.vma` is also called before setting sapt
"to ensure that preceding updates to the page table have completed,
and ensures that preceding loads and stores use the old page table,
not the new one"
=> `TRAMPOLINE` page uses it before entering user space
=> RISC-V CPUs can have different TLBs for different address spaces
=> avoid flushing an entire TLB
=> xv6 doesn't use this feature
Physical memory management consists of handling memory pages from the `kmem.freelist` table.
An allocation is materialized by removing an entry from this table,
freeing a page is about adding back the page to the list.
sbrk is implemented with the function `growproc` (in prog.c) which calls either uvmalloc or uvmdealloc.
Function signatures (for reference):
void kvminit(void); // set the kernel root page table
pagetable_t kvmmake(void); // create the kernel page table
void kvmmap(pagetable_t, uint64 virt@, uint64 phy@, uint64 sz, int perm); // add PTEs to the kernel page table
(this is only a call to mappages + a call to "panic" in case of an error)
=> `kvmmap` is a simple overlay for `mappages` (automatically calls `panic` if an error occurs)
int mappages(pagetable_t, uint64 virt@, uint64 size, uint64 phy@, int perm); // create PTEs
pte_t * walk(pagetable_t pagetable, uint64 va, int alloc); // virt@ -> PTE
=> `kvmmap` is a simple overlay for this function (automatically calls `panic` if an error occurs)
=> uses walk to find a PTE based on a virt@ (`walk` can also allocate a page in a page table)
pte_t * walk(pagetable_t pagetable, uint64 va, int alloc); // virt@ -> PTE (can allocate it if 'alloc' is set)
=> return the address of the PTE in the lowest layer in the tree
uint64 walkaddr(pagetable_t pagetable, uint64 va); // virt@ -> phy@
void proc_mapstacks(pagetable_t kpgtbl); // allocate a kernel stack for a process
3.8 Code: exec
exec = syscall replacing a process's user address space with data read from a file
= related files: kernel/{elf.h,exec.c}
1. open a file with `namei`
2. read ELF header (see kernel/elf.h for more info) matching structure `elfhdr`
3. read subsequent ELF section headers corresponding to the `proghdr` structure
each of them describing a part of the application that must be loaded into memory
xv6 only has two program section headers: instructions and data
How exec works
1. check if the file actually is an ELF binary
2. allocate a new page table with no user mapping with `proc_pagetable` (kernel/exec.c:49)
3. allocate memory for each ELF segment with `uvmalloc` (kernel/exec.c:65)
4. load each segment with `loadseg` (kernel/exec.c:10)
`loadseg` uses `readi` to read from the file
`loadseg` uses `walkaddr` to find the phy@ of the allocated memory at which to write ELF segments
To read sections from a binary: objdump -p file