Skip to content
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

Add Sorted Iterator for the UEFI Memory Map (Issue#661) #662

Merged
merged 2 commits into from
Mar 19, 2023
Merged

Add Sorted Iterator for the UEFI Memory Map (Issue#661) #662

merged 2 commits into from
Mar 19, 2023

Conversation

JarlEvanson
Copy link
Contributor

Added a sorted iterator and a method to convert a normal iterator to it.

Should close issue#661

@phip1611
Copy link
Contributor

@nicholasbishop I think we want a test for this. Do you think a unit-test with a "fake memory map" is the way to go, an integration test, or both?

I'd prefer a minimal unit test at least and an integration test in addition.

@phip1611 phip1611 changed the title Issue#661 Add Sorted Iterator for the UEFI Memory Map (Issue#661) Feb 18, 2023
@nicholasbishop
Copy link
Contributor

I have an alternative suggestion for how to implement this. While I agree with #661 (comment) that usually there shouldn't be a large number of entries so the speed of the sort shouldn't matter that much, I think we can avoid having to make that assumption while also just making the API nicer to use in general.

General outline:
Add a new type for the return value of BootServices::memory_map, something like:

pub struct MemoryMap<'a> {
    key: MemoryMapKey,
    buf: &'a mut [u8],
    entry_size: usize,
    len: usize,
}

impl<'a> MemoryMap<'a> {
    pub fn key(&self) -> MemoryMapKey { ... }
    pub fn sort(&mut self) { ... }
    pub fn entries(&self) -> MemoryMapIter { ... }
}

The caller can then decide if they care about sorting the map before iterating, and the sort can operate in-place on the buffer rather than the slower method of sorting within the iterator.

Does that seem reasonable? I haven't actually tried implementing this, but I think it should work.

@JarlEvanson
Copy link
Contributor Author

That seems doable. I'll try it and get back to you.

}
}

/// Hoare partition scheme for quicksort
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Hoare partition scheme for quicksort
/// Hoare partition scheme for quicksort.

}

/// Hoare partition scheme for quicksort
/// Must be called with `low` and `high` being indices within bounds
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Must be called with `low` and `high` being indices within bounds
/// Must be called with `low` and `high` being indices within bounds.

/// Sorts the memory map in place
pub fn sort(&mut self) {
unsafe {
self.qsort(0, self.len - 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't we use some built-in basic sorting functionality? It seems overkill to implement quick sort, doesn't it? What do you think, @nicholasbishop ?

I thought about a basic bubble sort as a UEFI memory map is unlikely to have thousands of entries.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the built-in sorting functions allow an unspecified constant spacing between elements in a slice, which would complicate using any of the built in basic sorting functions

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about pulling in a dep on the index-sort crate? It's a small crate with a very short commit history, but it seems like exactly what we need for this use case. I would prefer that to implementing any of our own sorts, as that seems somewhat outside the scope of a uefi crate.

(I was a little surprised that I couldn't find more crates related to sorting non-slice data structures, but I guess it is kind of a niche need.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh oops, just realized that crate isn't no-std. I might put up a PR for that.

Copy link
Contributor

@nicholasbishop nicholasbishop Feb 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I put up a PR in that project to make it no-std compatible.

I don't want to block this PR on it though. I do feel a little uncomfortable about adding a somewhat complicated sort implementation here, but I'm OK with it for now because we can hopefully replace it in the future with a dependency on an external crate.

I think most of the time it would probably be fine to use a simpler and slower sort algorithm (insertion sort can be implemented in very few lines for example), but we don't have any way to guarantee that the number of entries is small. I could imagine for example that some ill-behaved driver accidentally creates a bunch of allocations; it's the kind of thing that might not be noticed in testing since the time that boot services are active is usually pretty short.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the record, this is the PR nicholas set up: arlencox/index-sort#1

@phip1611
Copy link
Contributor

Thanks for working on this. LGTM and I think we are close to merging this.

Why would a user want an unsorted map? Should we sort the map automatically at any time?

}

impl<'buf> MemoryMap<'buf> {
#[allow(clippy::must_use_candidate)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than adding an allow for must_use_candidate here and elsewhere, add #[must_use].

self.key
}

/// Sorts the memory map in place
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should say here exactly what the memory map is sorted by (physical address? virtual address?)


impl<'buf> MemoryMap<'buf> {
#[allow(clippy::must_use_candidate)]
/// Returns the unique [`MemoryMapKey`] associated with the memory map
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docstrings and comments should end with a period.


/// Hoare partition scheme for quicksort
/// Must be called with `low` and `high` being indices within bounds
unsafe fn qsort(&mut self, low: usize, high: usize) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The qsort, partition, and swap functions should be made safe; they may do some unsafe operations internally but calling them shouldn't be unsafe.

}

unsafe fn partition(&mut self, low: usize, high: usize) -> usize {
let pivot = (self.buf.as_mut_ptr() as usize + (low + (high - low) / 2) * self.entry_size)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a function like this:

    fn get_elem_phys_addr(&self, index: usize) -> PhysicalAddress {
        let offset = index.checked_mul(self.entry_size).unwrap();
        let elem = unsafe { &*self.buf.as_ptr().add(offset).cast::<MemoryDescriptor>() };
        elem.phys_start
    }

Then the three places where you do pointer arithmetic in this function can be replaced with a much simpler call to this function.

(base + index1 * self.entry_size) as *mut u8,
(base + index2 * self.entry_size) as *mut u8,
self.entry_size,
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Casting from pointer -> integer -> pointer can be avoided here like this:

        let base = self.buf.as_mut_ptr();

        unsafe {
            ptr::swap_nonoverlapping(
                base.add(index1 * self.entry_size),
                base.add(index2 * self.entry_size),
                self.entry_size,
            );
        }

(I noticed this from some Miri warnings, see also #666 which will make these warnings into hard errors.)

@phip1611
Copy link
Contributor

LGTM! I clean up the git history (remove merging main into this branch) and merge it then. Thanks for the contribution!

Jarl Evanson and others added 2 commits March 19, 2023 20:20
This adds a MemoryMap type that iterates over UEFI memory descriptors.
The map is only sorted when one manually calls the sort method, as it may
take up a few seconds, if the memory map contains thousands of entries,
which may be the case on large systems.
@phip1611 phip1611 merged commit 6b730d5 into rust-osdev:main Mar 19, 2023
@JarlEvanson JarlEvanson deleted the issue#661 branch October 21, 2023 18:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement sorting of the memory map produced by exit_boot_services
3 participants