-
Notifications
You must be signed in to change notification settings - Fork 133
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
Create a separate PhysMemAllocator trait #30
Conversation
Thanks for the PR, @mark-i-m! What kind of compile errors do you mean? |
/// An allocator of physical pages of memory. | ||
pub trait PhysMemAllocator { | ||
/// Allocate a physical frame of size `S` if possible. | ||
fn alloc<S: PageSize>(&mut self) -> Option<PhysFrame>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The generic S
parameter is not used. I think you meant to use PhysFrame<S>
as return type.
src/structures/paging/recursive.rs
Outdated
{ | ||
use structures::paging::PageTableFlags as Flags; | ||
|
||
if entry.is_unused() { | ||
if let Some(frame) = allocator() { | ||
if let Some(frame) = allocator.alloc::<S>() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A page table is always 4KiB large, so it does not make sense to call this function with a generic S
.
I'm a bit unsure about this PR, because it requires much more than needed from the frame_allocator arguments. For example, we only need to allocate 4KiB frames in So we can no longer do things like the following: let mut frames = [None; 3];
for frame in &mut frames {
*frame = Some(frame_allocator.allocate_frame());
}
// do something with `frame_allocator`
recursive_page_table.map_to(page, frame, flags, || {
frames.iter_mut().find(|f| f.is_some()).take()
}); Here we preallocate three frames and use a closure around the |
On the current master, I get: $ cargo check
Checking x86_64 v0.2.0-alpha-019 (file:///nobackup/x86_64)
error[E0507]: cannot move out of borrowed content
--> src/structures/paging/recursive.rs:201:9
|
201 | allocator(frame);
| ^^^^^^^^^ cannot move out of borrowed content
error[E0507]: cannot move out of borrowed content
--> src/structures/paging/recursive.rs:315:9
|
315 | allocator(frame);
| ^^^^^^^^^ cannot move out of borrowed content
error[E0507]: cannot move out of borrowed content
--> src/structures/paging/recursive.rs:449:9
|
449 | allocator(frame);
| ^^^^^^^^^ cannot move out of borrowed content
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0507`.
error: Could not compile `x86_64`.
To learn more, run the command again with --verbose. I'm not really sure where my brain was yesterday... Thanks for pointing out those issues. Fixing them simplified a few things, too. Re: your concerns in #30 (comment) These are fair concerns. I had kind of imagined a different setup: One physical memory allocator for the whole kernel (a simple buddy allocator). This allocates backing pages for page tables, as well as normal pages, including huge pages. Also, I can't really imagine an allocator that doesn't know how to free memory; alloc and free just go together in my mind. For example, I can imagine such an allocator being an |
That's strange. For me and travis it compiles fine. I made some recent changes, could you try again with the current master?
This should be possible too, of course. You should be able to do: recursive_page_table.map_to(page, frame, flags, || {
buddy_allocator.allocate_frame()
}); |
Works now :)
That's true, but it seems like a really round-about way of doing it. The only purpose of the closure is as an abstraction. Traits seem like a more idiomatic way of accomplishing that. Would the following interface address your concerns: trait PhyMemAlloc<S: PageSize> {
fn alloc(&mut self) -> Option<PhysFrame<S>>;
}
trait PhyMemFree<S: PageSize> {
fn free(&mut self, frame: PhysFrame<S>);
}
fn map_to<A>(
&mut self,
page: Page<S>,
frame: PhysFrame<S>,
flags: PageTableFlags,
allocator: &mut A,
) -> Result<MapperFlush<S>, MapToError>
where
A: PhysMemAlloc<Size4KiB>; EDIT: lol, I forgot the allocator |
I would be fine with a I think I would choose different names, maybe Another thing that I just noticed: The |
I think that would conflate two different things: there is the page that was unmapped, but there are also possibly a few more pages freed that used to be used for page tables. I agree that the former should be returned to the caller, but I think it makes more sense for the latter to be freed to the allocator. |
The thing is that we don't free any page table frames, because there is no simple and fast way to check whether a page table is empty. A function that returns a frame and takes no allocator would be less confusing in this regard. |
Oh, hmm... that's a bit troubling. There are a lot of cases, where one knows that all mappings in some range will be reclaimed, so attempting to reclaim page tables makes sense (e.g. process termination). Perhaps we could add another method |
@4e554c4c said that he had figured out a way of quickly checking if a page table is empty, I believe. |
Actually, checking present bits is not sufficient because there could be shared pages. Linux solved this problem with struct page and reverse mappings. It seems that a bit more thought is required to come up with an api that allows genericly plugging in a way to find out if you should reclaim a page... |
The problem is that that all approaches are opinionated, so this library is
not the right place to handle this in my opinion.
I think the best way forward is:
- Return a the unmapped frame from `unmap` instead of freeing it in an allocator. This allows the user to decide whether that frame should really be freed. So they could use a reverse mapping, additional page table bits or some other mechanism to identify shared frames.
- Add some sort of `reclaim_unused_page_tables` method that frees unused page table frames in an allocator. The method could take an address range as argument to limit the scanning to a part of the table. This method could be called by the user if they know that there should be reclaimable tables,
e.g. after they unmapped a process. Alternatively, it can be called regularly by a background thread.
What do you think?
|
That sounds reasonable to me. I will try to come around to updating the PR later today... |
@mark-i-m Perfect, thank you! |
@phil-opp Is it ok to use |
(Still WIP, don't review yet) |
@phil-opp Let me know what you think. I'm not sure how to test this, so if you have any ideas, let me know. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the update, @mark-i-m!
The changes to unmap
look good for me, apart from the two comments I left.
I'm not sure about the design of the reclaim_page_tables
function. It seems a bit dangerous (it's possible to reclaim page tables that are still in use) and surprising (e.g. if a 1GiB range is passed the bounds are rounded so that not all page tables in the range might get freed).
Could you split off the reclaim_page_table
changes to a separate PR? This would allow us to quickly merge the unmap
change and release version 0.2.0. Adding reclaim_page_tables
later is not a breaking change, so we don't need to block the release on this.
src/structures/paging/frame_alloc.rs
Outdated
/// A trait for types that can allocate a frame of memory. | ||
pub trait FrameAllocator { | ||
/// Allocate a frame of the appropriate size and return it if possible. | ||
fn alloc<S: PageSize>(&mut self) -> Option<PhysFrame<S>>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should move the generic paramenter S
to the trait, otherwise every implementation must be able to allocate frames of all possible sizes (would this even be possible?).
src/structures/paging/frame_alloc.rs
Outdated
/// A trait for types that can deallocate a frame of memory. | ||
pub trait FrameDeallocator { | ||
/// Deallocate the given frame of memory. | ||
fn dealloc<S: PageSize>(&mut self, frame: PhysFrame<S>); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above:
We should move the generic paramenter
S
to the trait, otherwise every implementation must be able to allocate frames of all possible sizes (would this even be possible?).
src/structures/paging/recursive.rs
Outdated
|
||
/// Removes a mapping from the page table and returns the frame that used to be mapped. | ||
/// | ||
/// Note that no page tables or pages are deallocated. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean with "or pages"? How about "Note that no page table frames are deallocated."?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Meaning pages other than page tables.
We already rely on unstable features, so I
I'm planning to test it with a minimal kernel and the upcoming |
@phil-opp Do you mean that requiring an up-to-date nightly is okay, or not okay? |
@lachlansneff Oh sorry, I meant to say that it's okay to use up-to-date nightly features. Thanks for the hint, I updated my comment. |
Done. I will open another PR soon. |
Thanks a lot! |
I just updated the bootloader to the new trait based interface: pub(crate) fn map_page<'a, S>(
page: Page<S>,
phys_frame: PhysFrame<S>,
flags: PageTableFlags,
page_table: &mut RecursivePageTable<'a>,
frame_allocator: &mut FrameAllocator,
) -> Result<MapperFlush<S>, MapToError>
where
S: PageSize,
RecursivePageTable<'a>: Mapper<S>,
{
- page_table.map_to(page, phys_frame, flags, &mut || {
- frame_allocator.allocate_frame(MemoryRegionType::PageTable)
- })
+ struct PageTableAllocator<'a, 'b: 'a>(&'a mut FrameAllocator<'b>);
+
+ impl<'a, 'b> paging::FrameAllocator<Size4KiB> for PageTableAllocator<'a, 'b> {
+ fn alloc(&mut self) -> Option<PhysFrame<Size4KiB>> {
+ self.0.allocate_frame(MemoryRegionType::PageTable)
+ }
+ }
+
+ page_table.map_to(page, phys_frame, flags, &mut PageTableAllocator(frame_allocator))
} It's much more boilerplate now and we need two explicit lifetimes. So I think I liked the closure based interface more. I just wanted to bring it up here, maybe we can improve ergonomics somehow. (Commit rust-osdev/bootloader@83ae580#diff-9c959995cefca11a6ba2160e735da057R161) |
@phil-opp I'm confused. Why not just |
The problem is that I guess that's a relatively niche use case, but if similar cases appear we might think about implementing the trait for |
Interesting. What does MemoryRegionType do? Why can't you allocate an arbitrary frame of physical memory? This problem suggests that perhaps FrameAllocator should take some sort of |
We do allocate an arbitrary memory frame, we just remember for what it is used to create the bootinfo when control is passed to the kernel. This is really an uncommon use case, so adding an options argument is not worth it. |
Actually, when I thought about it, I don't think this is an unusual use case: https://github.com/torvalds/linux/blob/master/include/linux/gfp.h |
Ah, good point. So I guess we have three options:
I don't like option 1 because we would need to also add the option parameter to The difference between option 2 and 3 is whether the caller must explicitly define a frame allocator or can implicitly use any closure that returns frames. The question is if there's any danger from this implicitness, i.e. is it possible pass a non-allocator closure by accident. |
I'll comment more later, but I wanted to leave some notes in addition to your comments: If you want to use a third-party allocator, you still need to create a wrapper type that impls the trait. This is a bit annoying. We might consider adding some stock implementations. The one I wrote is here: We could also consider a NoAllocator implementation of FrameAllocator, where any allocation just cause a panic. This could be used when we want to ensure there is no allocation (e.g. under memory pressure or in the memory reclamation algorithm itself (to prevent recursion (to prevent recursion (to prevent recursion (...))))). |
I propose to more clearly separate out the notion of a Physical Frame Allocator. This PR creates a new PhysMemAllocator trait and uses it in the recursive page table. This also happens to solve a couple of compile errors around borrowing closures.
r? @phil-opp