-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
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
Comments for "https://os.phil-opp.com/paging-implementation/" #570
Comments
The |
This paging approach seems much clear and easy to understand |
@DanGdl Great to hear that! |
That's better than last time (I hope). Last time the code didn't compile. |
@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 |
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. |
@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. |
For anyone who is updating |
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" |
Discussed in #593 |
Just wanted to mention that the |
@nrlsk Thanks for reporting! Fixed in 6db5ad7. |
@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? |
@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 |
@tom7980 Thanks for elaborating!
I just want to ensure that xbuild/bootimage works on all OSs. If there is some configuration problem with Windows we could at least document it to help others.
Let me/us know if you have any questions, we're always happy to help! |
" We can't start the region at 28 KiB because it would collide with the already mapped page at 1004 MiB." Is that "1004 Mib" should be "1000 Kib" ? |
In Temporary Mapping:
Should it be |
@phil-opp Thanks for your great efforts in making the wonderful series! I have a few probably silly questions:
Appreciate your response. Thanks for your time! |
|
@Barronli You're welcome, I'm glad you like it! The complete-mapping mechanism is only used for accessing physical memory. There can be other virtual mappings in the page table that point to the same physical frames. For example, the VGA text buffer is mapped at both Regarding debugging, there are a few useful techniques. For example, you can use QEMU's I hope this helps! |
@phil-opp Before I get to my comment - great set of posts - incredibly simple to understand and follow and a good intro to rust. Can see a lot of efforts that you have put into this - can't appreciate enough. Now onto some failures that I see with updated dependencies: Building bootloader error: aborting due to previous error error: Could not compile Did I miss something / do something wrong? |
@siebenHeaven Thanks for the praise, it's great to hear that you like it! I already encountered this error a few times. I think the problem is that cargo-xbuild fails to remove old versions of the sysroot after the target JSON changed, but I haven't had the time to debug it yet. As a workaround, try to do a |
@phil-opp Thanks for the instant help, that worked! |
I got around the type awkwardness / iterator inefficiency for the allocator as follows: pub struct BootInfoFrameAllocator<T: Iterator<Item = PhysFrame>>(T);
impl BootInfoFrameAllocator<core::iter::Empty<PhysFrame>> {
pub unsafe fn new(memory_map: &'static MemoryMap)
-> impl FrameAllocator<Size4KiB>
{
BootInfoFrameAllocator(
memory_map.iter()
.filter(|r| r.region_type == MemoryRegionType::Usable)
.map(|r| r.range.start_addr()..r.range.end_addr())
.flat_map(|r| r.step_by(4096))
.map(|addr| PhysFrame::containing_address(PhysAddr::new(addr)))
)
}
}
unsafe impl<T> FrameAllocator<Size4KiB> for BootInfoFrameAllocator<T>
where T: Iterator<Item = PhysFrame>
{
fn allocate_frame(&mut self) -> Option<PhysFrame> {
self.0.next()
}
} It's not perfect, and implementing let mut frame_allocator = unsafe {
memory::BootInfoFrameAllocator::new(&boot_info.memory_map)
}; |
@htbf We used this approach in the first version of the post. The problem is that the generic type can't be named since it contains closures. For this reason, we changed it to the non-generic version to allow storing the I plan to switch back to this version as soon as the Permit impl Trait in type aliases RFC is fully implemented. Then we could use an approach like this to store a generic |
Also when you're running 32bit userspace under a 64bit kernel, full legacy behaviour still applies, so all segments can have a nonzero base if you have a particularly evil application developer, or its something like a DOS emulator and playing around with 16bit code. |
True. I meant when you're fully running in 64-bit mode, but I agree that it's important to be exact here. I updated my comment to ensure that no one gets confused by it. |
So, if I stick to 64-bit mode (I'm still in kernelspace and have no
processes yet) how do I walk the page table to find the faulting
address? So far as I know x86_64 doesn't let me do that.
…On 7/27/20, Philipp Oppermann ***@***.***> wrote:
True. I meant when you're fully running in 64-bit mode, but I agree that
it's important to be exact here. I updated my comment to ensure that no one
gets confused by it.
--
You are receiving this because you commented.
Reply to this email directly or view it on GitHub:
#570 (comment)
--
Signed,
Ethin D. Probst
|
The processor gave you the faulting linear address in Examples of legitimate pagefaults would be an access to a frame which is currently paged out to disk (at which point you've got to pause the task, page it back in from disk, edit the PTE to point at the new frame, then resume the task), or a write to a Copy-on-Write page (at which point you've got to allocate a new page, copy the old contents, and adjust the PTE from the faulting process to be read/write and point at the copy). Examples of illegitimate accesses might be a write to a read-only location (which isn't Copy on Write), or a kernel access to userspace mappings when SMEP/SMAP is active, etc. Unless you've started implementing any demand-faulting logic for code (which you probably haven't done, if you don't have userspace yet), then pagefaults should typically be fatal. |
I am getting an error:
It seems that the last page is huge. #[macro_export]
macro_rules! phmem_offset {
() => {
x86_64::VirtAddr::new(PHBASE.load(Ordering::SeqCst) as u64)
};
}
fn translate_inner(addr: VirtAddr) -> Option<PhysAddr> {
let (level_4_table_frame, _) = Cr3::read();
let table_indexes = [
addr.p4_index(),
addr.p3_index(),
addr.p2_index(),
addr.p1_index(),
];
let mut frame = level_4_table_frame;
for &index in &table_indexes {
let virt = phmem_offset!() + frame.start_address().as_u64();
let table_ptr: *const PageTable = virt.as_ptr();
let table = unsafe { &*table_ptr };
let entry = &table[index];
frame = match entry.frame() {
Ok(frame) => frame,
Err(FrameError::FrameNotPresent) => return None,
Err(FrameError::HugeFrame) => return Some(frame.start_address() + addr.as_u64() % (frame.size())),
};
}
Some(frame.start_address() + u64::from(addr.page_offset()))
} the phmem_offset!() macro is there because i use an AtomicUsize to store my physical base (i modified the |
Also, writing to zero is a very bad idea. Here is why:
and rust: #[inline]
#[stable(feature = "volatile", since = "1.9.0")]
pub unsafe fn write_volatile<T>(dst: *mut T, src: T) {
if cfg!(debug_assertions) && !is_aligned_and_not_null(dst) {
// Not panicking to keep codegen impact smaller.
abort();
}
// SAFETY: the caller must uphold the safety contract for `volatile_store`.
unsafe {
intrinsics::volatile_store(dst, src);
}
} Of course, "to keep the codegen impact smaller", we won't panic, but instead abort (so basically asm!("ud2"); ). |
To be clear, that is from the |
Writing to zero basically will cause a lot of suffering and confusion. |
Nevermind, in the example it's writing to 400. |
Thanks for sharing your code, it is much simpler to handle huge pages than I thought! We should update this in the post. I opened #852 to track this.
You're completely right. I opened #851 to track this and I try to update the post to use a different page soon.
That's great! Would you mind sharing your code? I'm currently working on a new bootloader version with UEFI support that will use a pixel-based framebuffer by default (see https://github.com/rust-osdev/bootloader/tree/uefi). For consistency, this means that we also want to default to a pixel-based for the BIOS implementation. Right now, we only support a small 320x200 framebuffer in the BIOS implementation, so your implementation might be useful for us. |
I completly disabled bootloader's printer interface and changed config_video_mode to: config_video_mode:
push bx
push ax
mov ax, 0x4F02
mov bx, 0x4192
int 0x10
pop bx
pop ax
ret I then asked it to map me all the RAM, and used that to write to the framebuffer which is at 0xfd000000, at least in QEMU. I have no clue how compatible this is. |
Looking at First of all, architecturally, a page address also isn't valid if it has any reserved bits set in addition to the present bit. For long mode paging, reserved bits are any address bits beyond MAXPHYSADDR, the NX bit if EFER.NXE is clear, any low order address bits on a large page except for PAT (bit 12), and very irritatingly, GLOBAL on the 4th level (but only on AMD hardware. Intel permits this bit to be set). Very occasionally, logic deliberately sets a reserved bit (usually 51) and puts non-address metadata in bits 50:1. This trick is no longer safe now that some large servers implement all 52 physical address bits. Secondly, and more importantly, PSE doesn't exist in a 4k page, and the bit is the PAT bit instead. Specifically, the PAT bit moves position depending on whether the page is large or not, and a 4k page isn't large by virtual of being the 4th step on the pagewalk, not by its bit layout. The currently logic will confuse the a non-WB cache type on the mapping as a large page. ~Andrew P.S. if x86 pagetables look like they're an organic mess grown over the past 30 years, that's perhaps because they are 😅 |
So, I was doing very well following your tutorial, (thanks for the work, much appreciated!), until just after the section on "Translating Addresses". I now get the following:
I actually have 55 errors total, but they are similar to or copies of E0658. Errors seem to be all related to compiling of x86_64. I have been able to compile and run every part of the tutorial up till this error. It feels like I underwent a system environment change that created this error for me. The only distinct thing I did around the time this error popped up though was to update Ruby and install Substrate, the blockchain building tool that uses Rust. Not sure if maybe something in that installation upended my environment. I have checked all toolchain as best I could, making sure I'm still using nightly Rust, rustc, cargo, etc. I tried adding the crate attributes as the compiler error suggested but got nowhere. Any advice before I try a full reset/restart? |
Further note: I tried various versions of x86_64, including the most recent 0.12.2 and several earlier versions. Nothing changed. |
Try updating your rustc version. The latest rustc should work with x86_64 version 0.12.2 I think. |
@bjorn3 Tried it, nothing changed - still have the same errors. |
@quaesaevum You still have version |
@phil-opp Thanks! That was it exactly. |
Now that x86/amd64 is dead can you make these tutorials but for ARM64? Paging is very different on arm. |
I don't have plans to port these tutorials to ARM64 anytime soon. I'd rather invest my time in continuing this series (e.g. threads, userspace, etc). After all, x86 is still the dominant architecture on consumer systems. |
And well, x86 has an ugly but at least somewhat specified and generic interface to the hardware. I don't think there's any equivalent to UEFI in ARM64 land, yet. Also, the OS course I took still uses 32 bit (with Simics, or an old crash box computer dedicated to it - boots using CDs), because it teaches the right concepts with less registers to guet wrong, and a somewhat simpler handling of setting up paging (which is a bit more complicated under x86_64, as page tables have to be set up before jumping to long mode). Once you have this knowledge of concepts, you should be able to deal with other platforms. (Or not because they have horrendous documentation when it comes to devices etc) |
UEFI exists for ARM64 too. In the past everyone was using their own firmware and device interfaces, but as far as I know ARM nowadays is pushing for some standarization to make it more suitable for use on servers. |
I've been trying to beef up my page fault handler to actually allocate new pages in some cases (e.g. if the kernel stack needs to grow and it's safe to do so). But I'm having trouble (a lot of trouble) trying to figure that out, because I need to have some frame allocator to allocate frames that back those pages. In the examples here (and in the heap section), you have a frame allocator which is created by kernel_main, and gets handed off to other functions as needed. But I don't see a way to use an approach like that for my page fault handler, as I won't be able to pick up the allocator from the kernel main function. Nor can I just instantiate an allocator in the page fault handler, since then the allocator that gets instantiated with zero knowledge of what frames are already allocated. I have tried to make a static allocator with a mutex in front of it, but that then runs into lifetime issues - I'm really bad at understanding lifetimes, but I think it's because the compiler wants the allocator to have a static lifetime but that struct requires data that doesn't have a static lifetime (in this case, coming from the BootInformation struct I'm loading with the multiboot2 crate). So I'm pretty much stuck - either I have memory unsafety because I'm not using a single global allocator, or I can't compile due to lifetime incompatibilities. Any advice on getting out of this conundrum? |
When I reached the end of paging-implementation,
If I do that it no longer compiles.
(Every other post ends with code that passes At this point, we should delete: // src/memory.rs
/// A FrameAllocator that always returns `None`.
pub struct EmptyFrameAllocator;
unsafe impl FrameAllocator<Size4KiB> for EmptyFrameAllocator {
fn allocate_frame(&mut self) -> Option<PhysFrame> {
None
}
} to avoid accidentally invoking undefined behavior, as explained above. If we remove |
@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. |
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.
|
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
This is a general purpose comment thread for the Paging Implementation post.
The text was updated successfully, but these errors were encountered: