* [PATCH v4 0/3] rust: alloc: add KVVec shrinking method
@ 2026-02-12 8:17 Shivam Kalra via B4 Relay
2026-02-12 8:17 ` [PATCH v4 1/3] rust: kvec: implement shrink_to for KVVec Shivam Kalra via B4 Relay
` (2 more replies)
0 siblings, 3 replies; 8+ messages in thread
From: Shivam Kalra via B4 Relay @ 2026-02-12 8:17 UTC (permalink / raw)
To: Danilo Krummrich, Lorenzo Stoakes, Vlastimil Babka,
Liam R. Howlett, Uladzislau Rezki, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Greg Kroah-Hartman,
Arve Hjønnevåg, Todd Kjos, Christian Brauner,
Carlos Llamas
Cc: rust-for-linux, linux-kernel, Shivam Kalra
This series adds a shrink_to() method to KVVec to allow explicit
capacity reduction for memory reclamation.
Problem:
When elements are removed from a KVVec, the allocated capacity is not
reduced. The underlying C allocators (krealloc/kvrealloc) don't shrink
memory in-place. This can cause significant memory waste in scenarios
with variable workloads.
Solution:
- Patch 1: Implements shrink_to(min_capacity, flags) method specifically
for KVVec (Vec<T, KVmalloc>). Shrinking only occurs when the operation
would free at least one page of memory. The implementation uses
explicit alloc+copy+free since vrealloc doesn't yet support in-place
shrinking. A TODO comment marks this for future replacement with a
generic implementation using A::realloc() once allocators properly
support shrinking.
- Patch 2: Adds KUnit tests for the new shrink_to method, verifying
page-based shrinking, empty vector handling, no-op behavior, and
min_capacity respect.
- Patch 3: Uses shrink_to() in the Rust binder driver to reclaim memory
when processes are deregistered. Uses a conservative shrinking
strategy (trigger at len < cap / 4, shrink to cap / 2) to avoid
shrink-then-regrow oscillation.
Testing:
- KUnit tests pass (rust_kvec test suite).
- Kernel boots successfully in QEMU with CONFIG_ANDROID_BINDER_IPC_RUST=y.
Changes since v3:
- Dropped the Shrinkable trait entirely - it was not needed for the
KVVec-specific implementation. (Danilo Krummrich)
- Removed shrink_to_fit() method. Only shrink_to() is implemented.
(Danilo Krummrich)
- Implemented shrink_to() only for KVVec (impl<T> Vec<T, KVmalloc>)
instead of as a generic method, since that covers the actual use
case in binder. (Danilo Krummrich, Alice Ryhl)
- Added TODO comment explaining this is a temporary KVVec-specific
implementation that should be replaced with a generic
Vec<T, A>::shrink_to() calling A::realloc() once the underlying
allocators properly support shrinking via realloc. (Danilo Krummrich)
- Changed binder shrink strategy to be less aggressive to avoid
shrink-then-regrow oscillation: now triggers when len < cap / 4 and
shrinks to cap / 2. (Alice Ryhl)
- Removed the cap > 128 threshold check in binder. (Alice Ryhl)
- Fixed commit prefix from "rust: binder:" to "rust_binder:".
(Alice Ryhl)
- Updated test suite to focus on KVVec-specific behavior.
Changes since v2:
- Introduced new Shrinkable marker trait to distinguish allocators that
benefit from shrinking (Vmalloc) from those that don't (Kmalloc).
- Shrinking now only occurs for vmalloc-backed buffers when at least one
page can be freed, avoiding unnecessary work for kmalloc buffers.
(Suggested by Danilo Krummrich)
- Split into 4 patches: Shrinkable trait introduction is now a
separate patch to improve reviewability.
- Uses explicit alloc+copy+free since vrealloc doesn't yet support
in-place shrinking; TODO added for future optimization when vrealloc
gains that capability. (Discussed with Danilo Krummrich)
- Fixed minor KVVec/KVec terminology (binder uses KVVec not KVec).
- Expanded KUnit tests to cover different allocator backends and
page-boundary shrinking behavior.
Changes since v1:
- Resend with correct threading (no code changes).
- Removed base-commit.
- Added explicit lore link to dependency in cover letter.
[1] https://lore.kernel.org/lkml/20260130205424.261700-1-shivamklr@cock.li/
[2] https://lore.kernel.org/lkml/20260131154016.270385-1-shivamklr@cock.li/
[3] https://lore.kernel.org/lkml/20260207-binder-shrink-vec-v3-v3-0-8ff388563427@cock.li/
Signed-off-by: Shivam Kalra <shivamkalra98@zohomail.in>
---
Shivam Kalra (3):
rust: kvec: implement shrink_to for KVVec
rust: alloc: add KUnit tests for KVVec shrink_to
rust_binder: shrink all_procs when deregistering processes
drivers/android/binder/context.rs | 11 +++
rust/kernel/alloc/kvec.rs | 190 +++++++++++++++++++++++++++++++++++++-
2 files changed, 200 insertions(+), 1 deletion(-)
---
base-commit: 3c4ae63073d84abee5d81ce46d86a94e9dae9c89
change-id: 20260212-binder-shrink-vec-v3-c8c1131efbf7
Best regards,
--
Shivam Kalra <shivamkalra98@zohomail.in>
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v4 1/3] rust: kvec: implement shrink_to for KVVec
2026-02-12 8:17 [PATCH v4 0/3] rust: alloc: add KVVec shrinking method Shivam Kalra via B4 Relay
@ 2026-02-12 8:17 ` Shivam Kalra via B4 Relay
2026-02-12 10:08 ` Danilo Krummrich
2026-02-12 8:17 ` [PATCH v4 2/3] rust: alloc: add KUnit tests for KVVec shrink_to Shivam Kalra via B4 Relay
2026-02-12 8:17 ` [PATCH v4 3/3] rust_binder: shrink all_procs when deregistering processes Shivam Kalra via B4 Relay
2 siblings, 1 reply; 8+ messages in thread
From: Shivam Kalra via B4 Relay @ 2026-02-12 8:17 UTC (permalink / raw)
To: Danilo Krummrich, Lorenzo Stoakes, Vlastimil Babka,
Liam R. Howlett, Uladzislau Rezki, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Greg Kroah-Hartman,
Arve Hjønnevåg, Todd Kjos, Christian Brauner,
Carlos Llamas
Cc: rust-for-linux, linux-kernel, Shivam Kalra
From: Shivam Kalra <shivamkalra98@zohomail.in>
Implement shrink_to method specifically for `KVVec` (i.e., `Vec<T, KVmalloc>`).
`shrink_to` reduces the vector's capacity to a specified minimum, but
only performs shrinking when it would free at least one page of memory.
This prevents unnecessary allocations while allowing reclamation of
unused memory.
The implementation uses explicit alloc+copy+free because `vrealloc`
does not yet support in-place shrinking. A TODO note marks this for
future replacement with a generic shrink_to for all allocators that
uses `A::realloc()` once the underlying allocators properly support
shrinking via realloc.
Suggested-by: Alice Ryhl <aliceryhl@google.com>
Suggested-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Shivam Kalra <shivamkalra98@zohomail.in>
---
rust/kernel/alloc/kvec.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 77 insertions(+), 1 deletion(-)
diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs
index ac8d6f763ae81..8524f9f3dff0a 100644
--- a/rust/kernel/alloc/kvec.rs
+++ b/rust/kernel/alloc/kvec.rs
@@ -9,7 +9,7 @@
};
use crate::{
fmt,
- page::AsPageIter, //
+ page::{AsPageIter, PAGE_SIZE},
};
use core::{
borrow::{Borrow, BorrowMut},
@@ -734,6 +734,82 @@ pub fn retain(&mut self, mut f: impl FnMut(&mut T) -> bool) {
self.truncate(num_kept);
}
}
+// TODO: This is a temporary KVVec-specific implementation. It should be replaced with a generic
+// `shrink_to()` for `impl<T, A: Allocator> Vec<T, A>` that uses `A::realloc()` once the
+// underlying allocators properly support shrinking via realloc.
+impl<T> Vec<T, KVmalloc> {
+ /// Shrinks the capacity of the vector with a lower bound.
+ ///
+ /// The capacity will remain at least as large as both the length and the supplied value.
+ /// If the current capacity is less than the lower limit, this is a no-op.
+ ///
+ /// Shrinking only occurs if the operation would free at least one page of memory.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// // Allocate enough capacity to span multiple pages.
+ /// let elements_per_page = kernel::page::PAGE_SIZE / core::mem::size_of::<u32>();
+ /// let mut v = KVVec::with_capacity(elements_per_page * 4, GFP_KERNEL)?;
+ /// v.push(1, GFP_KERNEL)?;
+ /// v.push(2, GFP_KERNEL)?;
+ ///
+ /// v.shrink_to(0, GFP_KERNEL)?;
+ /// # Ok::<(), Error>(())
+ /// ```
+ pub fn shrink_to(&mut self, min_capacity: usize, flags: Flags) -> Result<(), AllocError> {
+ let target_cap = core::cmp::max(self.len(), min_capacity);
+
+ if self.capacity() <= target_cap {
+ return Ok(());
+ }
+
+ if Self::is_zst() {
+ return Ok(());
+ }
+
+ // Only shrink if we would free at least one page.
+ let current_size = self.capacity() * core::mem::size_of::<T>();
+ let target_size = target_cap * core::mem::size_of::<T>();
+ let current_pages = current_size.div_ceil(PAGE_SIZE);
+ let target_pages = target_size.div_ceil(PAGE_SIZE);
+
+ if current_pages <= target_pages {
+ return Ok(());
+ }
+
+ if target_cap == 0 {
+ if !self.layout.is_empty() {
+ // SAFETY: `self.ptr` was allocated with `KVmalloc`, layout matches.
+ unsafe { KVmalloc::free(self.ptr.cast(), self.layout.into()) };
+ }
+ self.ptr = NonNull::dangling();
+ self.layout = ArrayLayout::empty();
+ return Ok(());
+ }
+
+ // SAFETY: `target_cap <= self.capacity()` and original capacity was valid.
+ let new_layout = unsafe { ArrayLayout::<T>::new_unchecked(target_cap) };
+
+ // TODO: Once vrealloc supports in-place shrinking (mm/vmalloc.c:4316), this
+ // explicit alloc+copy+free can potentially be replaced with realloc.
+ let new_ptr = KVmalloc::alloc(new_layout.into(), flags, NumaNode::NO_NODE)?;
+
+ // SAFETY: Both pointers are valid, non-overlapping, and properly aligned.
+ unsafe {
+ ptr::copy_nonoverlapping(self.as_ptr(), new_ptr.as_ptr().cast::<T>(), self.len);
+ }
+
+ // SAFETY: `self.ptr` was allocated with `KVmalloc`, layout matches.
+ unsafe { KVmalloc::free(self.ptr.cast(), self.layout.into()) };
+
+ // SAFETY: `new_ptr` is non-null because `KVmalloc::alloc` succeeded.
+ self.ptr = unsafe { NonNull::new_unchecked(new_ptr.as_ptr().cast::<T>()) };
+ self.layout = new_layout;
+
+ Ok(())
+ }
+}
impl<T: Clone, A: Allocator> Vec<T, A> {
/// Extend the vector by `n` clones of `value`.
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v4 2/3] rust: alloc: add KUnit tests for KVVec shrink_to
2026-02-12 8:17 [PATCH v4 0/3] rust: alloc: add KVVec shrinking method Shivam Kalra via B4 Relay
2026-02-12 8:17 ` [PATCH v4 1/3] rust: kvec: implement shrink_to for KVVec Shivam Kalra via B4 Relay
@ 2026-02-12 8:17 ` Shivam Kalra via B4 Relay
2026-02-12 8:17 ` [PATCH v4 3/3] rust_binder: shrink all_procs when deregistering processes Shivam Kalra via B4 Relay
2 siblings, 0 replies; 8+ messages in thread
From: Shivam Kalra via B4 Relay @ 2026-02-12 8:17 UTC (permalink / raw)
To: Danilo Krummrich, Lorenzo Stoakes, Vlastimil Babka,
Liam R. Howlett, Uladzislau Rezki, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Greg Kroah-Hartman,
Arve Hjønnevåg, Todd Kjos, Christian Brauner,
Carlos Llamas
Cc: rust-for-linux, linux-kernel, Shivam Kalra
From: Shivam Kalra <shivamkalra98@zohomail.in>
Add comprehensive KUnit tests for the shrink_to method for KVVec.
The tests verify:
- Basic shrinking from multiple pages to fewer pages with data integrity
preservation
- Empty vector shrinking to zero capacity
- No-op behavior when shrinking to a larger capacity than current
- Respect for min_capacity parameter when larger than vector length
These tests ensure that the shrinking logic correctly identifies when
memory can be reclaimed (by freeing at least one page) and that data
integrity is maintained throughout shrink operations.
Signed-off-by: Shivam Kalra <shivamkalra98@zohomail.in>
---
rust/kernel/alloc/kvec.rs | 112 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 112 insertions(+)
diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs
index 8524f9f3dff0..d28f8fccd30f 100644
--- a/rust/kernel/alloc/kvec.rs
+++ b/rust/kernel/alloc/kvec.rs
@@ -1474,4 +1474,116 @@ fn add(value: &mut [bool]) {
func.push_within_capacity(false).unwrap();
}
}
+
+ #[test]
+ fn test_kvvec_shrink_to() {
+ use crate::page::PAGE_SIZE;
+
+ // Calculate elements per page for u32.
+ let elements_per_page = PAGE_SIZE / core::mem::size_of::<u32>();
+
+ // Create a vector with capacity spanning multiple pages.
+ let mut v = KVVec::<u32>::with_capacity(elements_per_page * 4, GFP_KERNEL).unwrap();
+
+ // Add a few elements.
+ v.push(1, GFP_KERNEL).unwrap();
+ v.push(2, GFP_KERNEL).unwrap();
+ v.push(3, GFP_KERNEL).unwrap();
+
+ let initial_capacity = v.capacity();
+ assert!(initial_capacity >= elements_per_page * 4);
+
+ // Shrink to a capacity that would free at least one page.
+ v.shrink_to(elements_per_page, GFP_KERNEL).unwrap();
+
+ // Capacity should have been reduced.
+ assert!(v.capacity() < initial_capacity);
+ assert!(v.capacity() >= elements_per_page);
+
+ // Elements should be preserved.
+ assert_eq!(v.len(), 3);
+ assert_eq!(v[0], 1);
+ assert_eq!(v[1], 2);
+ assert_eq!(v[2], 3);
+
+ // Shrink to zero (should shrink to len).
+ v.shrink_to(0, GFP_KERNEL).unwrap();
+
+ // Capacity should be at least the length.
+ assert!(v.capacity() >= v.len());
+
+ // Elements should still be preserved.
+ assert_eq!(v.len(), 3);
+ assert_eq!(v[0], 1);
+ assert_eq!(v[1], 2);
+ assert_eq!(v[2], 3);
+ }
+
+ #[test]
+ fn test_kvvec_shrink_to_empty() {
+ use crate::page::PAGE_SIZE;
+
+ let elements_per_page = PAGE_SIZE / core::mem::size_of::<u64>();
+
+ // Create a vector with large capacity but no elements.
+ let mut v = KVVec::<u64>::with_capacity(elements_per_page * 4, GFP_KERNEL).unwrap();
+
+ assert!(v.is_empty());
+ let initial_capacity = v.capacity();
+
+ // Shrink empty vector to zero.
+ v.shrink_to(0, GFP_KERNEL).unwrap();
+
+ // Should have freed the allocation.
+ assert!(v.capacity() < initial_capacity);
+ assert!(v.is_empty());
+ }
+
+ #[test]
+ fn test_kvvec_shrink_to_no_op() {
+ use crate::page::PAGE_SIZE;
+
+ let elements_per_page = PAGE_SIZE / core::mem::size_of::<u32>();
+
+ // Create a small vector.
+ let mut v = KVVec::<u32>::with_capacity(elements_per_page, GFP_KERNEL).unwrap();
+ v.push(1, GFP_KERNEL).unwrap();
+
+ let capacity_before = v.capacity();
+
+ // Try to shrink to a capacity larger than current - should be no-op.
+ v.shrink_to(capacity_before + 100, GFP_KERNEL).unwrap();
+
+ assert_eq!(v.capacity(), capacity_before);
+ assert_eq!(v.len(), 1);
+ assert_eq!(v[0], 1);
+ }
+
+ #[test]
+ fn test_kvvec_shrink_to_respects_min_capacity() {
+ use crate::page::PAGE_SIZE;
+
+ let elements_per_page = PAGE_SIZE / core::mem::size_of::<u32>();
+
+ // Create a vector with large capacity.
+ let mut v = KVVec::<u32>::with_capacity(elements_per_page * 4, GFP_KERNEL).unwrap();
+
+ // Add some elements.
+ for i in 0..10 {
+ v.push(i, GFP_KERNEL).unwrap();
+ }
+
+ // Shrink to a min_capacity larger than length.
+ let min_cap = elements_per_page * 2;
+ v.shrink_to(min_cap, GFP_KERNEL).unwrap();
+
+ // Capacity should be at least min_capacity.
+ assert!(v.capacity() >= min_cap);
+
+ // All elements preserved.
+ assert_eq!(v.len(), 10);
+ for i in 0..10 {
+ assert_eq!(v[i as usize], i);
+ }
+ }
}
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v4 3/3] rust_binder: shrink all_procs when deregistering processes
2026-02-12 8:17 [PATCH v4 0/3] rust: alloc: add KVVec shrinking method Shivam Kalra via B4 Relay
2026-02-12 8:17 ` [PATCH v4 1/3] rust: kvec: implement shrink_to for KVVec Shivam Kalra via B4 Relay
2026-02-12 8:17 ` [PATCH v4 2/3] rust: alloc: add KUnit tests for KVVec shrink_to Shivam Kalra via B4 Relay
@ 2026-02-12 8:17 ` Shivam Kalra via B4 Relay
2026-02-13 16:19 ` Alice Ryhl
2 siblings, 1 reply; 8+ messages in thread
From: Shivam Kalra via B4 Relay @ 2026-02-12 8:17 UTC (permalink / raw)
To: Danilo Krummrich, Lorenzo Stoakes, Vlastimil Babka,
Liam R. Howlett, Uladzislau Rezki, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Greg Kroah-Hartman,
Arve Hjønnevåg, Todd Kjos, Christian Brauner,
Carlos Llamas
Cc: rust-for-linux, linux-kernel, Shivam Kalra
From: Shivam Kalra <shivamkalra98@zohomail.in>
When a process is deregistered from the binder context, the all_procs
vector may have significant unused capacity. Add logic to shrink the
vector using a conservative strategy that prevents shrink-then-regrow
oscillation.
The shrinking strategy triggers when length drops below 1/4 of capacity,
and shrinks to 1/2 of capacity rather than to the exact length. This
provides hysteresis to avoid repeated reallocations when the process
count fluctuates.
The shrink operation uses GFP_KERNEL and is allowed to fail gracefully
since it is purely an optimization. The vector remains valid and
functional even if shrinking fails.
Suggested-by: Alice Ryhl <aliceryhl@google.com>
Signed-off-by: Shivam Kalra <shivamkalra98@zohomail.in>
---
drivers/android/binder/context.rs | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/drivers/android/binder/context.rs b/drivers/android/binder/context.rs
index 9cf437c025a20..399dab475728b 100644
--- a/drivers/android/binder/context.rs
+++ b/drivers/android/binder/context.rs
@@ -94,6 +94,17 @@ pub(crate) fn deregister_process(self: &Arc<Self>, proc: &Arc<Process>) {
}
let mut manager = self.manager.lock();
manager.all_procs.retain(|p| !Arc::ptr_eq(p, proc));
+
+ // Shrink the vector if it has significant unused capacity to avoid memory waste,
+ // but use a conservative strategy to prevent shrink-then-regrow oscillation.
+ // Only shrink when length drops below 1/4 of capacity, and shrink to 1/2 capacity.
+ let len = manager.all_procs.len();
+ let cap = manager.all_procs.capacity();
+ if len < cap / 4 {
+ // Shrink to half capacity. Ignore allocation failures since this is just an
+ // optimization; the vector remains valid even if shrinking fails.
+ let _ = manager.all_procs.shrink_to(cap / 2, GFP_KERNEL);
+ }
}
pub(crate) fn set_manager_node(&self, node_ref: NodeRef) -> Result {
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v4 1/3] rust: kvec: implement shrink_to for KVVec
2026-02-12 8:17 ` [PATCH v4 1/3] rust: kvec: implement shrink_to for KVVec Shivam Kalra via B4 Relay
@ 2026-02-12 10:08 ` Danilo Krummrich
2026-02-13 13:00 ` Shivam Kalra
0 siblings, 1 reply; 8+ messages in thread
From: Danilo Krummrich @ 2026-02-12 10:08 UTC (permalink / raw)
To: Shivam Kalra via B4 Relay
Cc: shivamkalra98, Lorenzo Stoakes, Vlastimil Babka, Liam R. Howlett,
Uladzislau Rezki, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Greg Kroah-Hartman, Arve Hjønnevåg,
Todd Kjos, Christian Brauner, Carlos Llamas, rust-for-linux,
linux-kernel
On Thu Feb 12, 2026 at 9:17 AM CET, Shivam Kalra via B4 Relay wrote:
> diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs
> index ac8d6f763ae81..8524f9f3dff0a 100644
> --- a/rust/kernel/alloc/kvec.rs
> +++ b/rust/kernel/alloc/kvec.rs
> @@ -9,7 +9,7 @@
> };
> use crate::{
> fmt,
> - page::AsPageIter, //
> + page::{AsPageIter, PAGE_SIZE},
Please keep the kernel import style [1].
[1] https://docs.kernel.org/rust/coding-guidelines.html#imports
> };
> use core::{
> borrow::{Borrow, BorrowMut},
> @@ -734,6 +734,82 @@ pub fn retain(&mut self, mut f: impl FnMut(&mut T) -> bool) {
> self.truncate(num_kept);
> }
> }
> +// TODO: This is a temporary KVVec-specific implementation. It should be replaced with a generic
> +// `shrink_to()` for `impl<T, A: Allocator> Vec<T, A>` that uses `A::realloc()` once the
> +// underlying allocators properly support shrinking via realloc.
> +impl<T> Vec<T, KVmalloc> {
> + /// Shrinks the capacity of the vector with a lower bound.
> + ///
> + /// The capacity will remain at least as large as both the length and the supplied value.
> + /// If the current capacity is less than the lower limit, this is a no-op.
> + ///
> + /// Shrinking only occurs if the operation would free at least one page of memory.
Since this is now specific to Vec<T, KVmalloc>, the correct statement would be
that for kmalloc() allocations realloc() takes the decision and for vmalloc()
allocations it will only be shrunk if at least one page can be freed. Please
also add that in the vmalloc() case, we are doing a deep copy.
> + ///
> + /// # Examples
> + ///
> + /// ```
> + /// // Allocate enough capacity to span multiple pages.
> + /// let elements_per_page = kernel::page::PAGE_SIZE / core::mem::size_of::<u32>();
> + /// let mut v = KVVec::with_capacity(elements_per_page * 4, GFP_KERNEL)?;
> + /// v.push(1, GFP_KERNEL)?;
> + /// v.push(2, GFP_KERNEL)?;
> + ///
> + /// v.shrink_to(0, GFP_KERNEL)?;
> + /// # Ok::<(), Error>(())
> + /// ```
> + pub fn shrink_to(&mut self, min_capacity: usize, flags: Flags) -> Result<(), AllocError> {
> + let target_cap = core::cmp::max(self.len(), min_capacity);
> +
> + if self.capacity() <= target_cap {
> + return Ok(());
> + }
> +
> + if Self::is_zst() {
> + return Ok(());
> + }
We should check for is_vmalloc_addr() here and just call realloc() if it is
false.
The reason is that this will ensure that the semantics of this function will be
as close as possible to the final implementation, which will fully rely on
realloc().
I also understand that calling realloc() if !is_vmalloc_addr() seems
superfluous, but I don't want this code to make assumptions about the semantics
of the backing allocators.
> + // Only shrink if we would free at least one page.
> + let current_size = self.capacity() * core::mem::size_of::<T>();
> + let target_size = target_cap * core::mem::size_of::<T>();
> + let current_pages = current_size.div_ceil(PAGE_SIZE);
> + let target_pages = target_size.div_ceil(PAGE_SIZE);
> +
> + if current_pages <= target_pages {
> + return Ok(());
> + }
> +
> + if target_cap == 0 {
> + if !self.layout.is_empty() {
> + // SAFETY: `self.ptr` was allocated with `KVmalloc`, layout matches.
> + unsafe { KVmalloc::free(self.ptr.cast(), self.layout.into()) };
> + }
> + self.ptr = NonNull::dangling();
> + self.layout = ArrayLayout::empty();
> + return Ok(());
> + }
> +
> + // SAFETY: `target_cap <= self.capacity()` and original capacity was valid.
> + let new_layout = unsafe { ArrayLayout::<T>::new_unchecked(target_cap) };
> +
> + // TODO: Once vrealloc supports in-place shrinking (mm/vmalloc.c:4316), this
> + // explicit alloc+copy+free can potentially be replaced with realloc.
I'd drop this comment as it reads as if this function should be changed, even
though the TODO comment above already explains that it should be replaced.
> + let new_ptr = KVmalloc::alloc(new_layout.into(), flags, NumaNode::NO_NODE)?;
> +
> + // SAFETY: Both pointers are valid, non-overlapping, and properly aligned.
If a safety comment justifies multiple things, we usually structure it as a
list.
> + unsafe {
> + ptr::copy_nonoverlapping(self.as_ptr(), new_ptr.as_ptr().cast::<T>(), self.len);
NIT: Please move the semicolon at the end of the unsafe block.
> + }
> +
> + // SAFETY: `self.ptr` was allocated with `KVmalloc`, layout matches.
Same here.
> + unsafe { KVmalloc::free(self.ptr.cast(), self.layout.into()) };
> +
> + // SAFETY: `new_ptr` is non-null because `KVmalloc::alloc` succeeded.
> + self.ptr = unsafe { NonNull::new_unchecked(new_ptr.as_ptr().cast::<T>()) };
> + self.layout = new_layout;
> +
> + Ok(())
> + }
> +}
>
> impl<T: Clone, A: Allocator> Vec<T, A> {
> /// Extend the vector by `n` clones of `value`.
>
> --
> 2.43.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v4 1/3] rust: kvec: implement shrink_to for KVVec
2026-02-12 10:08 ` Danilo Krummrich
@ 2026-02-13 13:00 ` Shivam Kalra
0 siblings, 0 replies; 8+ messages in thread
From: Shivam Kalra @ 2026-02-13 13:00 UTC (permalink / raw)
To: Danilo Krummrich, Shivam Kalra via B4 Relay
Cc: Lorenzo Stoakes, Vlastimil Babka, Liam R. Howlett,
Uladzislau Rezki, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Greg Kroah-Hartman, Arve Hjønnevåg,
Todd Kjos, Christian Brauner, Carlos Llamas, rust-for-linux,
linux-kernel
On 12/02/26 15:38, Danilo Krummrich wrote:
> On Thu Feb 12, 2026 at 9:17 AM CET, Shivam Kalra via B4 Relay wrote:
>> diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs
>> index ac8d6f763ae81..8524f9f3dff0a 100644
>> --- a/rust/kernel/alloc/kvec.rs
>> +++ b/rust/kernel/alloc/kvec.rs
>> @@ -9,7 +9,7 @@
>> };
>> use crate::{
>> fmt,
>> - page::AsPageIter, //
>> + page::{AsPageIter, PAGE_SIZE},
>
> Please keep the kernel import style [1].
>
> [1] https://docs.kernel.org/rust/coding-guidelines.html#imports
>
>> };
>> use core::{
>> borrow::{Borrow, BorrowMut},
>> @@ -734,6 +734,82 @@ pub fn retain(&mut self, mut f: impl FnMut(&mut T) -> bool) {
>> self.truncate(num_kept);
>> }
>> }
>> +// TODO: This is a temporary KVVec-specific implementation. It should be replaced with a generic
>> +// `shrink_to()` for `impl<T, A: Allocator> Vec<T, A>` that uses `A::realloc()` once the
>> +// underlying allocators properly support shrinking via realloc.
>> +impl<T> Vec<T, KVmalloc> {
>> + /// Shrinks the capacity of the vector with a lower bound.
>> + ///
>> + /// The capacity will remain at least as large as both the length and the supplied value.
>> + /// If the current capacity is less than the lower limit, this is a no-op.
>> + ///
>> + /// Shrinking only occurs if the operation would free at least one page of memory.
>
> Since this is now specific to Vec<T, KVmalloc>, the correct statement would be
> that for kmalloc() allocations realloc() takes the decision and for vmalloc()
> allocations it will only be shrunk if at least one page can be freed. Please
> also add that in the vmalloc() case, we are doing a deep copy.
>
>> + ///
>> + /// # Examples
>> + ///
>> + /// ```
>> + /// // Allocate enough capacity to span multiple pages.
>> + /// let elements_per_page = kernel::page::PAGE_SIZE / core::mem::size_of::<u32>();
>> + /// let mut v = KVVec::with_capacity(elements_per_page * 4, GFP_KERNEL)?;
>> + /// v.push(1, GFP_KERNEL)?;
>> + /// v.push(2, GFP_KERNEL)?;
>> + ///
>> + /// v.shrink_to(0, GFP_KERNEL)?;
>> + /// # Ok::<(), Error>(())
>> + /// ```
>> + pub fn shrink_to(&mut self, min_capacity: usize, flags: Flags) -> Result<(), AllocError> {
>> + let target_cap = core::cmp::max(self.len(), min_capacity);
>> +
>> + if self.capacity() <= target_cap {
>> + return Ok(());
>> + }
>> +
>> + if Self::is_zst() {
>> + return Ok(());
>> + }
>
> We should check for is_vmalloc_addr() here and just call realloc() if it is
> false.
>
> The reason is that this will ensure that the semantics of this function will be
> as close as possible to the final implementation, which will fully rely on
> realloc().
>
> I also understand that calling realloc() if !is_vmalloc_addr() seems
> superfluous, but I don't want this code to make assumptions about the semantics
> of the backing allocators.
>
>> + // Only shrink if we would free at least one page.
>> + let current_size = self.capacity() * core::mem::size_of::<T>();
>> + let target_size = target_cap * core::mem::size_of::<T>();
>> + let current_pages = current_size.div_ceil(PAGE_SIZE);
>> + let target_pages = target_size.div_ceil(PAGE_SIZE);
>> +
>> + if current_pages <= target_pages {
>> + return Ok(());
>> + }
>> +
>> + if target_cap == 0 {
>> + if !self.layout.is_empty() {
>> + // SAFETY: `self.ptr` was allocated with `KVmalloc`, layout matches.
>> + unsafe { KVmalloc::free(self.ptr.cast(), self.layout.into()) };
>> + }
>> + self.ptr = NonNull::dangling();
>> + self.layout = ArrayLayout::empty();
>> + return Ok(());
>> + }
>> +
>> + // SAFETY: `target_cap <= self.capacity()` and original capacity was valid.
>> + let new_layout = unsafe { ArrayLayout::<T>::new_unchecked(target_cap) };
>> +
>> + // TODO: Once vrealloc supports in-place shrinking (mm/vmalloc.c:4316), this
>> + // explicit alloc+copy+free can potentially be replaced with realloc.
>
> I'd drop this comment as it reads as if this function should be changed, even
> though the TODO comment above already explains that it should be replaced.
>
>> + let new_ptr = KVmalloc::alloc(new_layout.into(), flags, NumaNode::NO_NODE)?;
>> +
>> + // SAFETY: Both pointers are valid, non-overlapping, and properly aligned.
>
> If a safety comment justifies multiple things, we usually structure it as a
> list.
>
>> + unsafe {
>> + ptr::copy_nonoverlapping(self.as_ptr(), new_ptr.as_ptr().cast::<T>(), self.len);
>
> NIT: Please move the semicolon at the end of the unsafe block.
>
>> + }
>> +
>> + // SAFETY: `self.ptr` was allocated with `KVmalloc`, layout matches.
>
> Same here.
>
>> + unsafe { KVmalloc::free(self.ptr.cast(), self.layout.into()) };
>> +
>> + // SAFETY: `new_ptr` is non-null because `KVmalloc::alloc` succeeded.
>> + self.ptr = unsafe { NonNull::new_unchecked(new_ptr.as_ptr().cast::<T>()) };
>> + self.layout = new_layout;
>> +
>> + Ok(())
>> + }
>> +}
>>
>> impl<T: Clone, A: Allocator> Vec<T, A> {
>> /// Extend the vector by `n` clones of `value`.
>>
>> --
>> 2.43.0
>
Thanks for the feedback.
I will soon send an update on the v5 patch.
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v4 3/3] rust_binder: shrink all_procs when deregistering processes
2026-02-12 8:17 ` [PATCH v4 3/3] rust_binder: shrink all_procs when deregistering processes Shivam Kalra via B4 Relay
@ 2026-02-13 16:19 ` Alice Ryhl
2026-02-14 7:42 ` Shivam Kalra
0 siblings, 1 reply; 8+ messages in thread
From: Alice Ryhl @ 2026-02-13 16:19 UTC (permalink / raw)
To: Shivam Kalra
Cc: Danilo Krummrich, Lorenzo Stoakes, Vlastimil Babka,
Liam R. Howlett, Uladzislau Rezki, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Greg Kroah-Hartman, Arve Hjønnevåg,
Todd Kjos, Christian Brauner, Carlos Llamas, rust-for-linux,
linux-kernel
On Thu, Feb 12, 2026 at 01:47:09PM +0530, Shivam Kalra wrote:
> When a process is deregistered from the binder context, the all_procs
> vector may have significant unused capacity. Add logic to shrink the
> vector using a conservative strategy that prevents shrink-then-regrow
> oscillation.
> The shrinking strategy triggers when length drops below 1/4 of capacity,
> and shrinks to 1/2 of capacity rather than to the exact length. This
> provides hysteresis to avoid repeated reallocations when the process
> count fluctuates.
> The shrink operation uses GFP_KERNEL and is allowed to fail gracefully
> since it is purely an optimization. The vector remains valid and
> functional even if shrinking fails.
>
> Suggested-by: Alice Ryhl <aliceryhl@google.com>
> Signed-off-by: Shivam Kalra <shivamkalra98@zohomail.in>
> ---
> drivers/android/binder/context.rs | 11 +++++++++++
> 1 file changed, 11 insertions(+)
>
> diff --git a/drivers/android/binder/context.rs b/drivers/android/binder/context.rs
> index 9cf437c025a20..399dab475728b 100644
> --- a/drivers/android/binder/context.rs
> +++ b/drivers/android/binder/context.rs
> @@ -94,6 +94,17 @@ pub(crate) fn deregister_process(self: &Arc<Self>, proc: &Arc<Process>) {
> }
> let mut manager = self.manager.lock();
> manager.all_procs.retain(|p| !Arc::ptr_eq(p, proc));
> +
> + // Shrink the vector if it has significant unused capacity to avoid memory waste,
> + // but use a conservative strategy to prevent shrink-then-regrow oscillation.
> + // Only shrink when length drops below 1/4 of capacity, and shrink to 1/2 capacity.
> + let len = manager.all_procs.len();
> + let cap = manager.all_procs.capacity();
> + if len < cap / 4 {
> + // Shrink to half capacity. Ignore allocation failures since this is just an
> + // optimization; the vector remains valid even if shrinking fails.
> + let _ = manager.all_procs.shrink_to(cap / 2, GFP_KERNEL);
Not a big deal, but perhaps we should write len*2 here instead of cap/2?
That way, if the cap got way far away from the length, it would equalize
right away.
Alice
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v4 3/3] rust_binder: shrink all_procs when deregistering processes
2026-02-13 16:19 ` Alice Ryhl
@ 2026-02-14 7:42 ` Shivam Kalra
0 siblings, 0 replies; 8+ messages in thread
From: Shivam Kalra @ 2026-02-14 7:42 UTC (permalink / raw)
To: Alice Ryhl
Cc: Danilo Krummrich, Lorenzo Stoakes, Vlastimil Babka,
Liam R. Howlett, Uladzislau Rezki, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Greg Kroah-Hartman, Arve Hjønnevåg,
Todd Kjos, Christian Brauner, Carlos Llamas, rust-for-linux,
linux-kernel
On 13/02/26 21:49, Alice Ryhl wrote:
> On Thu, Feb 12, 2026 at 01:47:09PM +0530, Shivam Kalra wrote:
>> When a process is deregistered from the binder context, the all_procs
>> vector may have significant unused capacity. Add logic to shrink the
>> vector using a conservative strategy that prevents shrink-then-regrow
>> oscillation.
>> The shrinking strategy triggers when length drops below 1/4 of capacity,
>> and shrinks to 1/2 of capacity rather than to the exact length. This
>> provides hysteresis to avoid repeated reallocations when the process
>> count fluctuates.
>> The shrink operation uses GFP_KERNEL and is allowed to fail gracefully
>> since it is purely an optimization. The vector remains valid and
>> functional even if shrinking fails.
>>
>> Suggested-by: Alice Ryhl <aliceryhl@google.com>
>> Signed-off-by: Shivam Kalra <shivamkalra98@zohomail.in>
>> ---
>> drivers/android/binder/context.rs | 11 +++++++++++
>> 1 file changed, 11 insertions(+)
>>
>> diff --git a/drivers/android/binder/context.rs b/drivers/android/binder/context.rs
>> index 9cf437c025a20..399dab475728b 100644
>> --- a/drivers/android/binder/context.rs
>> +++ b/drivers/android/binder/context.rs
>> @@ -94,6 +94,17 @@ pub(crate) fn deregister_process(self: &Arc<Self>, proc: &Arc<Process>) {
>> }
>> let mut manager = self.manager.lock();
>> manager.all_procs.retain(|p| !Arc::ptr_eq(p, proc));
>> +
>> + // Shrink the vector if it has significant unused capacity to avoid memory waste,
>> + // but use a conservative strategy to prevent shrink-then-regrow oscillation.
>> + // Only shrink when length drops below 1/4 of capacity, and shrink to 1/2 capacity.
>> + let len = manager.all_procs.len();
>> + let cap = manager.all_procs.capacity();
>> + if len < cap / 4 {
>> + // Shrink to half capacity. Ignore allocation failures since this is just an
>> + // optimization; the vector remains valid even if shrinking fails.
>> + let _ = manager.all_procs.shrink_to(cap / 2, GFP_KERNEL);
>
> Not a big deal, but perhaps we should write len*2 here instead of cap/2?
> That way, if the cap got way far away from the length, it would equalize
> right away.
>
> Alice
Thanks for the feedback. Will add this in v5.
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-02-14 7:42 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-12 8:17 [PATCH v4 0/3] rust: alloc: add KVVec shrinking method Shivam Kalra via B4 Relay
2026-02-12 8:17 ` [PATCH v4 1/3] rust: kvec: implement shrink_to for KVVec Shivam Kalra via B4 Relay
2026-02-12 10:08 ` Danilo Krummrich
2026-02-13 13:00 ` Shivam Kalra
2026-02-12 8:17 ` [PATCH v4 2/3] rust: alloc: add KUnit tests for KVVec shrink_to Shivam Kalra via B4 Relay
2026-02-12 8:17 ` [PATCH v4 3/3] rust_binder: shrink all_procs when deregistering processes Shivam Kalra via B4 Relay
2026-02-13 16:19 ` Alice Ryhl
2026-02-14 7:42 ` Shivam Kalra
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox