281 lines · 9.4 KB · Rust
RawDownload
// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2025 Google LLC. use kernel::{    page::{PAGE_MASK, PAGE_SIZE},    prelude::*,    seq_file::SeqFile,    seq_print,    task::Pid,}; use crate::range_alloc::{DescriptorState, FreedRange, Range}; /// Keeps track of allocations in a process' mmap.////// Each process has an mmap where the data for incoming transactions will be placed. This struct/// keeps track of allocations made in the mmap. For each allocation, we store a descriptor that/// has metadata related to the allocation. We also keep track of available free space.pub(super) struct ArrayRangeAllocator<T> {    /// This stores all ranges that are allocated. Unlike the tree based allocator, we do *not*    /// store the free ranges.    ///    /// Sorted by offset.    pub(super) ranges: KVec<Range<T>>,    size: usize,    free_oneway_space: usize,} struct FindEmptyRes {    /// Which index in `ranges` should we insert the new range at?    ///    /// Inserting the new range at this index keeps `ranges` sorted.    insert_at_idx: usize,    /// Which offset should we insert the new range at?    insert_at_offset: usize,} impl<T> ArrayRangeAllocator<T> {    pub(crate) fn new(size: usize, alloc: EmptyArrayAlloc<T>) -> Self {        Self {            ranges: alloc.ranges,            size,            free_oneway_space: size / 2,        }    }     pub(crate) fn free_oneway_space(&self) -> usize {        self.free_oneway_space    }     pub(crate) fn count_buffers(&self) -> usize {        self.ranges.len()    }     pub(crate) fn total_size(&self) -> usize {        self.size    }     pub(crate) fn is_full(&self) -> bool {        self.ranges.len() == self.ranges.capacity()    }     pub(crate) fn debug_print(&self, m: &SeqFile) -> Result<()> {        for range in &self.ranges {            seq_print!(                m,                "  buffer {}: {} size {} pid {} oneway {}",                0,                range.offset,                range.size,                range.state.pid(),                range.state.is_oneway(),            );            if let DescriptorState::Reserved(_) = range.state {                seq_print!(m, " reserved\n");            } else {                seq_print!(m, " allocated\n");            }        }        Ok(())    }     /// Find somewhere to put a new range.    ///    /// Unlike the tree implementation, we do not bother to find the smallest gap. The idea is that    /// fragmentation isn't a big issue when we don't have many ranges.    ///    /// Returns the index that the new range should have in `self.ranges` after insertion.    fn find_empty_range(&self, size: usize) -> Option<FindEmptyRes> {        let after_last_range = self.ranges.last().map(Range::endpoint).unwrap_or(0);         if size <= self.total_size() - after_last_range {            // We can put the range at the end, so just do that.            Some(FindEmptyRes {                insert_at_idx: self.ranges.len(),                insert_at_offset: after_last_range,            })        } else {            let mut end_of_prev = 0;            for (i, range) in self.ranges.iter().enumerate() {                // Does it fit before the i'th range?                if size <= range.offset - end_of_prev {                    return Some(FindEmptyRes {                        insert_at_idx: i,                        insert_at_offset: end_of_prev,                    });                }                end_of_prev = range.endpoint();            }            None        }    }     pub(crate) fn reserve_new(        &mut self,        debug_id: usize,        size: usize,        is_oneway: bool,        pid: Pid,    ) -> Result<(usize, bool)> {        // Compute new value of free_oneway_space, which is set only on success.        let new_oneway_space = if is_oneway {            match self.free_oneway_space.checked_sub(size) {                Some(new_oneway_space) => new_oneway_space,                None => return Err(ENOSPC),            }        } else {            self.free_oneway_space        };         let FindEmptyRes {            insert_at_idx,            insert_at_offset,        } = self.find_empty_range(size).ok_or(ENOSPC)?;        self.free_oneway_space = new_oneway_space;         let new_range = Range {            offset: insert_at_offset,            size,            state: DescriptorState::new(is_oneway, debug_id, pid),        };        // Insert the value at the given index to keep the array sorted.        self.ranges            .insert_within_capacity(insert_at_idx, new_range)            .ok()            .unwrap();         // Start detecting spammers once we have less than 20%        // of async space left (which is less than 10% of total        // buffer size).        //        // (This will short-circuit, so `low_oneway_space` is        // only called when necessary.)        let oneway_spam_detected =            is_oneway && new_oneway_space < self.size / 10 && self.low_oneway_space(pid);         Ok((insert_at_offset, oneway_spam_detected))    }     /// Find the amount and size of buffers allocated by the current caller.    ///    /// The idea is that once we cross the threshold, whoever is responsible    /// for the low async space is likely to try to send another async transaction,    /// and at some point we'll catch them in the act.  This is more efficient    /// than keeping a map per pid.    fn low_oneway_space(&self, calling_pid: Pid) -> bool {        let mut total_alloc_size = 0;        let mut num_buffers = 0;         // Warn if this pid has more than 50 transactions, or more than 50% of        // async space (which is 25% of total buffer size). Oneway spam is only        // detected when the threshold is exceeded.        for range in &self.ranges {            if range.state.is_oneway() && range.state.pid() == calling_pid {                total_alloc_size += range.size;                num_buffers += 1;            }        }        num_buffers > 50 || total_alloc_size > self.size / 4    }     pub(crate) fn reservation_abort(&mut self, offset: usize) -> Result<FreedRange> {        // This could use a binary search, but linear scans are usually faster for small arrays.        let i = self            .ranges            .iter()            .position(|range| range.offset == offset)            .ok_or(EINVAL)?;        let range = &self.ranges[i];         if let DescriptorState::Allocated(_) = range.state {            return Err(EPERM);        }         let size = range.size;        let offset = range.offset;         if range.state.is_oneway() {            self.free_oneway_space += size;        }         // This computes the range of pages that are no longer used by *any* allocated range. The        // caller will mark them as unused, which means that they can be freed if the system comes        // under memory pressure.        let mut freed_range = FreedRange::interior_pages(offset, size);        if offset % PAGE_SIZE != 0 {            if i == 0 || self.ranges[i - 1].endpoint() <= (offset & PAGE_MASK) {                freed_range.start_page_idx -= 1;            }        }        if range.endpoint() % PAGE_SIZE != 0 {            let page_after = (range.endpoint() & PAGE_MASK) + PAGE_SIZE;            if i + 1 == self.ranges.len() || page_after <= self.ranges[i + 1].offset {                freed_range.end_page_idx += 1;            }        }         self.ranges.remove(i)?;        Ok(freed_range)    }     pub(crate) fn reservation_commit(&mut self, offset: usize, data: &mut Option<T>) -> Result {        // This could use a binary search, but linear scans are usually faster for small arrays.        let range = self            .ranges            .iter_mut()            .find(|range| range.offset == offset)            .ok_or(ENOENT)?;         let DescriptorState::Reserved(reservation) = &range.state else {            return Err(ENOENT);        };         range.state = DescriptorState::Allocated(reservation.clone().allocate(data.take()));        Ok(())    }     pub(crate) fn reserve_existing(&mut self, offset: usize) -> Result<(usize, usize, Option<T>)> {        // This could use a binary search, but linear scans are usually faster for small arrays.        let range = self            .ranges            .iter_mut()            .find(|range| range.offset == offset)            .ok_or(ENOENT)?;         let DescriptorState::Allocated(allocation) = &mut range.state else {            return Err(ENOENT);        };         let data = allocation.take();        let debug_id = allocation.reservation.debug_id;        range.state = DescriptorState::Reserved(allocation.reservation.clone());        Ok((range.size, debug_id, data))    }     pub(crate) fn take_for_each<F: Fn(usize, usize, usize, Option<T>)>(&mut self, callback: F) {        for range in self.ranges.iter_mut() {            if let DescriptorState::Allocated(allocation) = &mut range.state {                callback(                    range.offset,                    range.size,                    allocation.reservation.debug_id,                    allocation.data.take(),                );            }        }    }} pub(crate) struct EmptyArrayAlloc<T> {    ranges: KVec<Range<T>>,} impl<T> EmptyArrayAlloc<T> {    pub(crate) fn try_new(capacity: usize) -> Result<Self> {        Ok(Self {            ranges: KVec::with_capacity(capacity, GFP_KERNEL)?,        })    }}