Paging Implementation #1013
Replies: 94 comments 12 replies
-
The |
Beta Was this translation helpful? Give feedback.
-
This paging approach seems much clear and easy to understand |
Beta Was this translation helpful? Give feedback.
-
@DanGdl Great to hear that! |
Beta Was this translation helpful? Give feedback.
-
That's better than last time (I hope). Last time the code didn't compile. |
Beta Was this translation helpful? Give feedback.
-
@pitust Please open an issue when you have any problems with compiling the code. You can also find the complete code for the post in the |
Beta Was this translation helpful? Give feedback.
-
If I use the 'map_physical_memory' feature,and I want to create a new level_4_page_table to be switched to when I create a new process.Do I need to map the whole physical memory in the new page table manually just like what you did in 'bootloader' crate?Actually I found that snippet of code,but I am not really sure should I type it again in my code. |
Beta Was this translation helpful? Give feedback.
-
@songzhi If you want that your new process also has access to the physical memory then you need to add a mapping for it in its address space. As an optimization you can link the level 3 or level 2 page table from your current address space to the new address space instead of copying them, so that both address spaces share the same level 3/level 2 table. |
Beta Was this translation helpful? Give feedback.
-
For anyone who is updating |
Beta Was this translation helpful? Give feedback.
-
How to create global static frame_allocator so that I can move the page mapping inside the page fault handler. I have tried In lib.rspub static mut FRAME_ALLOCATOR: Option<BootInfoFrameAllocator<Iterator<Item = PhysFrame>> = None I keep getting an error "doesn't have a size known at compile-time" |
Beta Was this translation helpful? Give feedback.
-
Discussed in #593 |
Beta Was this translation helpful? Give feedback.
-
Just wanted to mention that the |
Beta Was this translation helpful? Give feedback.
-
@nrlsk Thanks for reporting! Fixed in 6db5ad7. |
Beta Was this translation helpful? Give feedback.
-
@tom7980 Seems like you deleted your comment. It was a valid question, so let me give you a short answer anyway. Yes, the frame allocator could be implemented this way. We used a similar implementation before. The problem is that it couldn't be stored in a
Could you open an issue for this with more specific information? |
Beta Was this translation helpful? Give feedback.
-
@phil-opp I can open an issue about it tomorrow as I'm on the wrong computer currently, I was only struggling on my windows environment, once I booted into Linux and tried the build steps I managed to sort it so it might just be my windows env is mis-configured. Regarding my question, I actually had a go at implementing it but I came up against the issue that the initialisation of the wrapper was expecting a type I where I: Iterator, but due to passing a memory map to the function and doing some transformations onto it I was getting a nested iterator and it wouldn't let me store it anyway! I'm going to take another look at it tomorrow but I think I understand what you meant about the |
Beta Was this translation helpful? Give feedback.
-
@drzewiec typically you static-ize your frame handler in some way like this (this is from my kernel): lazy_static! {
static ref MAPPER: Mutex<Option<OffsetPageTable<'static>>> = Mutex::new(None);
static ref FRAME_ALLOCATOR: Mutex<Option<GlobalFrameAllocator>> = Mutex::new(None);
// ...
}
// Instantiate the mapper and frame allocators:
let mut mapper = MAPPER.lock();
let mut allocator = FRAME_ALLOCATOR.lock();
*mapper = Some(unsafe { init_mapper(physical_memory_offset) });
*allocator = Some(GlobalFrameAllocator::init(memory_map));
let end_addr = start_addr + size;
match (mapper.as_mut(), allocator.as_mut()) {
(Some(m), Some(a)) => allocate_paged_heap(start_addr, end_addr - start_addr, m, a),
_ => panic!("Cannot acquire mapper or frame allocator lock!"),
} You'll need a function to allocate a paged heap, of course, but this strategy should work. |
Beta Was this translation helpful? Give feedback.
-
Yeah, I feel like my issue may be trying to use the multiboot2 structs as part of my frame allocator. I am currently storing a MemoryAreaIter in there (so the frame allocator can go through the areas of memory that are marked as available by grub), and the compiler is complaining that my `'static` frame allocator outlives the boot info struct (which I can't think of a way to make static, even with lazy_static, since I need to initialize it with a memory address that grub places in a register).
I am wondering if I would have better luck with a different allocator design that isn't trying to store objects like that. I'm not sure, honestly lifetime issues are pretty hard for me to wrap my head around.
|
Beta Was this translation helpful? Give feedback.
-
Okay, so weird problem with memory management. // Below code is for context
static MAPPER: Lazy<TicketMutex<Option<OffsetPageTable<'static>>>> =
Lazy::new(|| TicketMutex::new(None));
static FRAME_ALLOCATOR: Lazy<TicketMutex<Option<GlobalFrameAllocator>>> =
Lazy::new(|| TicketMutex::new(None));
static MMAP: Once<Vec<MemoryRegion, 1024>> = Once::new();
// ...
// Memory usage, in frames
static MUSE: AtomicU64 = AtomicU64::new(0);
// Total system memory usage
static SMUSE: AtomicU64 = AtomicU64::new(0);
// System total RAM
static STOTAL: AtomicU64 = AtomicU64::new(0);
// Frame position counter
static FPOS: AtomicUsize = AtomicUsize::new(0);
// RSDP address
static RSDP: AtomicU64 = AtomicU64::new(0);
// ...
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct MemoryRegion {
pub start: u64,
pub end: u64,
pub kind: MemoryRegionKind,
}
/// Initializes the internal memory map.
#[cold]
pub fn init_memory_map(map: &'static MemoryRegions, rsdpaddr: u64) {
info!(
"Loading free memory region list from memory map at {:p}",
&map
);
MMAP.call_once(|| {
let mut mmap: Vec<MemoryRegion, 1024> = Vec::new();
map.iter().for_each(|region| {
mmap.push(MemoryRegion {
start: region.start,
end: region.end,
kind: region.kind,
})
.unwrap();
STOTAL.fetch_add(region.end - region.start, Ordering::Relaxed);
});
mmap
});
info!("Discovered {} bytes of RAM", STOTAL.load(Ordering::Relaxed));
info!("RSDP at {:X}", rsdpaddr);
RSDP.swap(rsdpaddr, Ordering::Relaxed);
}
// ...
#[derive(Debug, Copy, Clone)]
struct GlobalFrameAllocator;
impl GlobalFrameAllocator {
/// Initializes the global frame allocator
#[cold]
pub fn init() -> Self {
FPOS.store(0, Ordering::Relaxed);
GlobalFrameAllocator {}
}
}
unsafe impl FrameAllocator<Size4KiB> for GlobalFrameAllocator {
#[must_use]
fn allocate_frame(&mut self) -> Option<PhysFrame> {
if MMAP.is_completed() {
FPOS.fetch_add(1, Ordering::SeqCst);
return MMAP
.get()
.unwrap()
.iter()
.map(|r| r.start..r.end)
.flat_map(|r| r.step_by(4096))
.map(|addr| PhysFrame::containing_address(PhysAddr::new(addr)))
.nth(FPOS.load(Ordering::Relaxed));
} else {
warn!("Memory allocation attempted when MMAP was not ready");
warn!("Waiting for memory map to be ready...");
FPOS.fetch_add(1, Ordering::SeqCst);
return MMAP
.wait()
.iter()
.map(|r| r.start..r.end)
.flat_map(|r| r.step_by(4096))
.map(|addr| PhysFrame::containing_address(PhysAddr::new(addr)))
.nth(FPOS.load(Ordering::Relaxed));
}
}
}
impl FrameDeallocator<Size4KiB> for GlobalFrameAllocator {
unsafe fn deallocate_frame(&mut self, _: PhysFrame) {
FPOS.fetch_sub(1, Ordering::SeqCst);
}
}
/// Initializes the memory subsystem.
#[cold]
pub fn init(physical_memory_offset: u64, start_addr: u64, size: u64) {
let mut mapper = MAPPER.lock();
*mapper = Some(unsafe { init_mapper(physical_memory_offset) });
let mut allocator = FRAME_ALLOCATOR.lock();
*allocator = Some(GlobalFrameAllocator::init());
let end_addr = start_addr + size;
match (mapper.as_mut(), allocator.as_mut()) {
(Some(m), Some(a)) => allocate_paged_heap(start_addr, end_addr - start_addr, m, a),
_ => panic!("Memory allocator or page frame allocator failed creation!"),
}
}
// Triple fault occurs somewhere in either of these functions -- debugging it is proving to be quite challenging
#[cold]
unsafe fn init_mapper(physical_memory_offset: u64) -> OffsetPageTable<'static> {
// Get active L4 table
trace!(
"Retrieving active L4 table with memoffset {:X}",
physical_memory_offset
);
let (level_4_table, _) = get_active_l4_table(physical_memory_offset);
// initialize the mapper
OffsetPageTable::new(level_4_table, VirtAddr::new(physical_memory_offset))
}
#[cold]
pub fn allocate_paged_heap(
start: u64,
size: u64,
mapper: &mut impl Mapper<Size4KiB>,
frame_allocator: &mut impl FrameAllocator<Size4KiB>,
) {
// Construct a page range
let page_range = {
// Calculate start and end
let heap_start = VirtAddr::new(start);
let heap_end = heap_start + size - 1u64;
let heap_start_page = Page::containing_address(heap_start);
let heap_end_page = Page::containing_address(heap_end);
Page::range_inclusive(heap_start_page, heap_end_page)
};
// Allocate appropriate page frames
page_range.for_each(|page| {
let frame = match frame_allocator.allocate_frame() {
Some(f) => f,
None => panic!("Can't allocate frame!"),
};
let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE;
let frame2 = frame;
unsafe {
match mapper.map_to(page, frame, flags, frame_allocator) {
Ok(f) => {
f.flush();
MUSE.fetch_add(1, Ordering::Relaxed);
}
Err(e) => panic!(
"Cannot allocate frame range {:X}h-{:X}h: {:?}",
frame2.start_address().as_u64(),
frame2.start_address().as_u64() + frame2.size(),
e
),
}
}
});
SMUSE.fetch_add(size, Ordering::Relaxed);
} That's a lot of code in that code dump. Attempting to trace the triple fault is quite a challenging process because (for some reason) computer code tends to differ in behavior when a debugger is attached, something I don't understand, and so this bug sometimes occurs at some point and sometimes it doesn't. I'm not sure precisely how to track where this triple fault occurs due to two things:
Thoughts? |
Beta Was this translation helpful? Give feedback.
-
Also (I'm replying like this because I know that those reading this via email won't get edits) I do have the IDT and GDT loaded. I just haven't (yet) initialized the interrupt controller because I can't do that without the VMM. So I trust that the firmware hasn't left the APIC/X2APIC in an undefined state. |
Beta Was this translation helpful? Give feedback.
-
The bootloader would have to exit the boot services -- you couldn't
really do much otherwise that wasn't restricted to UEFI
protocols/services. (Though I do have a very interesting idea that
UeFI makes possible...)
…On 6/15/21, Philipp Oppermann ***@***.***> wrote:
Have you tried to pass `--no-reboot` to QEMU? Then it should exit in case of
a triple fault instead of restarting. It would be very useful to know the
first exception that ultimately causes the triple fault and using -d int is
probably the best way to find this out.
Also: Do you have set up a double fault handler?
--
You are receiving this because you commented.
Reply to this email directly or view it on GitHub:
#1013 (reply in thread)
--
Signed,
Ethin D. Probst
|
Beta Was this translation helpful? Give feedback.
-
I'm having a lot of difficulty debugging this page fault (I got the triple fault to at least work). After using step for a bit of time and then returning to "next" the next command starts behaving just like step. Instead of (say) jumping over these lines of my code, for example: return MMAP
.get()
.unwrap()
.iter()
.map(|r| r.start..r.end)
.flat_map(|r| r.step_by(4096))
.map(|addr| PhysFrame::containing_address(PhysAddr::new(addr)))
.nth(FPOS.load(Ordering::Relaxed)); It does this instead:
And so on and so forth. This makes it incredibly difficult to get out of and the debugging noise makes it impossible to track where I'm trying to go. Does anyone know why GDB is behaving this way? |
Beta Was this translation helpful? Give feedback.
-
Okay, I think I've nailed it down. For some reason, this code, or thereabouts, is causing the page fault: fn create_next_table<'b, A>(
&self,
entry: &'b mut PageTableEntry,
insert_flags: PageTableFlags,
allocator: &mut A,
) -> Result<&'b mut PageTable, PageTableCreateError>
where
A: FrameAllocator<Size4KiB> + ?Sized,
{
let created;
if entry.is_unused() {
if let Some(frame) = allocator.allocate_frame() {
entry.set_frame(frame, insert_flags);
created = true;
} else {
return Err(PageTableCreateError::FrameAllocationFailed);
}
} else {
if !insert_flags.is_empty() && !entry.flags().contains(insert_flags) {
entry.set_flags(entry.flags() | insert_flags);
}
created = false;
}
let page_table = match self.next_table_mut(entry) {
Err(PageTableWalkError::MappedToHugePage) => {
return Err(PageTableCreateError::MappedToHugePage);
}
Err(PageTableWalkError::NotMapped) => panic!("entry should be mapped at this point"),
Ok(page_table) => page_table,
};
if created {
page_table.zero();
}
Ok(page_table)
} To be specific, this part: let page_table = match self.next_table_mut(entry) {
Err(PageTableWalkError::MappedToHugePage) => {
return Err(PageTableCreateError::MappedToHugePage);
}
Err(PageTableWalkError::NotMapped) => panic!("entry should be mapped at this point"),
Ok(page_table) => page_table,
};
if created {
page_table.zero();
}
Ok(page_table) I've no idea why this is causing the page fault in particular. But for some reason this is where my (15 plus) breakpoints lead me to. Thoughts? |
Beta Was this translation helpful? Give feedback.
-
sir, I follow this post step by step. But after the session "Accessing the Page Tables", when I build and run it, the qemu window displays panic information.
The same as the source code cloned from your github repo. There is my develop enviroment: |
Beta Was this translation helpful? Give feedback.
-
hello, when i cargo run, i got a panic: |
Beta Was this translation helpful? Give feedback.
-
In "mapping the complete physical memory", can someone explain the 132KiB number? 32 GiB / 4 KiB = 8388608 pages Each level 2 page table has 2MiB / 8B = 262144 entries Therefore there are 8388608 / 262144 = 32 level 2 page tables, and 1 level 3 page table with 32 entries. Space occupied = |
Beta Was this translation helpful? Give feedback.
-
Before this post, we wrote bytes to 0xb8000 to print something on screen. Does that mean |
Beta Was this translation helpful? Give feedback.
-
I guess the bootloader is not necessary anymore once the kernel is properly loaded and starts executing, but the bootloader seems to identity-map the initial page tables and the bootloader itself's code and data sections as seen here at the 3rd stage and never unload or unmap them even until the control is transferred to the kernel. Is the bootloader code and data used to communicate with the kernel or for any other reasons after the kernel is loaded? |
Beta Was this translation helpful? Give feedback.
-
When I use cargo run to compile the kernel, why is it always stuck?I have to try it again and agian. |
Beta Was this translation helpful? Give feedback.
-
Hello, I have trouble understanding the code for the After playing a bit in Rust Playground, I think I understood. Are |
Beta Was this translation helpful? Give feedback.
-
Hello, In the technique Map the Complete Physical Memory, how can I create a new PageTable for another process. Since OffsetPageTable requires the that complete physical memory to be mapped, how do I achieve that? Thank you. |
Beta Was this translation helpful? Give feedback.
-
This is a general purpose comment thread for the Paging Implementation post.
Beta Was this translation helpful? Give feedback.
All reactions