Skip to content

Commit 2cdae41

Browse files
Burak EmirYuryNorov
authored andcommitted
rust: add dynamic ID pool abstraction for bitmap
This is a port of the Binder data structure introduced in commit 15d9da3 ("binder: use bitmap for faster descriptor lookup") to Rust. Like drivers/android/dbitmap.h, the ID pool abstraction lets clients acquire and release IDs. The implementation uses a bitmap to know what IDs are in use, and gives clients fine-grained control over the time of allocation. This fine-grained control is needed in the Android Binder. We provide an example that release a spinlock for allocation and unit tests (rustdoc examples). The implementation does not permit shrinking below capacity below BITS_PER_LONG. Suggested-by: Alice Ryhl <aliceryhl@google.com> Suggested-by: Yury Norov <yury.norov@gmail.com> Reviewed-by: Alice Ryhl <aliceryhl@google.com> Signed-off-by: Burak Emir <bqe@google.com> Signed-off-by: Yury Norov (NVIDIA) <yury.norov@gmail.com>
1 parent 38cc91d commit 2cdae41

File tree

3 files changed

+228
-0
lines changed

3 files changed

+228
-0
lines changed

MAINTAINERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4309,6 +4309,7 @@ R: Yury Norov <yury.norov@gmail.com>
43094309
S: Maintained
43104310
F: lib/find_bit_benchmark_rust.rs
43114311
F: rust/kernel/bitmap.rs
4312+
F: rust/kernel/id_pool.rs
43124313

43134314
BITOPS API
43144315
M: Yury Norov <yury.norov@gmail.com>

rust/kernel/id_pool.rs

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
// Copyright (C) 2025 Google LLC.
4+
5+
//! Rust API for an ID pool backed by a [`BitmapVec`].
6+
7+
use crate::alloc::{AllocError, Flags};
8+
use crate::bitmap::BitmapVec;
9+
10+
const BITS_PER_LONG: usize = bindings::BITS_PER_LONG as usize;
11+
12+
/// Represents a dynamic ID pool backed by a [`BitmapVec`].
13+
///
14+
/// Clients acquire and release IDs from unset bits in a bitmap.
15+
///
16+
/// The capacity of the ID pool may be adjusted by users as
17+
/// needed. The API supports the scenario where users need precise control
18+
/// over the time of allocation of a new backing bitmap, which may require
19+
/// release of spinlock.
20+
/// Due to concurrent updates, all operations are re-verified to determine
21+
/// if the grow or shrink is sill valid.
22+
///
23+
/// # Examples
24+
///
25+
/// Basic usage
26+
///
27+
/// ```
28+
/// use kernel::alloc::{AllocError, flags::GFP_KERNEL};
29+
/// use kernel::id_pool::IdPool;
30+
///
31+
/// let mut pool = IdPool::new(64, GFP_KERNEL)?;
32+
/// for i in 0..64 {
33+
/// assert_eq!(i, pool.acquire_next_id(i).ok_or(ENOSPC)?);
34+
/// }
35+
///
36+
/// pool.release_id(23);
37+
/// assert_eq!(23, pool.acquire_next_id(0).ok_or(ENOSPC)?);
38+
///
39+
/// assert_eq!(None, pool.acquire_next_id(0)); // time to realloc.
40+
/// let resizer = pool.grow_request().ok_or(ENOSPC)?.realloc(GFP_KERNEL)?;
41+
/// pool.grow(resizer);
42+
///
43+
/// assert_eq!(pool.acquire_next_id(0), Some(64));
44+
/// # Ok::<(), Error>(())
45+
/// ```
46+
///
47+
/// Releasing spinlock to grow the pool
48+
///
49+
/// ```no_run
50+
/// use kernel::alloc::{AllocError, flags::GFP_KERNEL};
51+
/// use kernel::sync::{new_spinlock, SpinLock};
52+
/// use kernel::id_pool::IdPool;
53+
///
54+
/// fn get_id_maybe_realloc(guarded_pool: &SpinLock<IdPool>) -> Result<usize, AllocError> {
55+
/// let mut pool = guarded_pool.lock();
56+
/// loop {
57+
/// match pool.acquire_next_id(0) {
58+
/// Some(index) => return Ok(index),
59+
/// None => {
60+
/// let alloc_request = pool.grow_request();
61+
/// drop(pool);
62+
/// let resizer = alloc_request.ok_or(AllocError)?.realloc(GFP_KERNEL)?;
63+
/// pool = guarded_pool.lock();
64+
/// pool.grow(resizer)
65+
/// }
66+
/// }
67+
/// }
68+
/// }
69+
/// ```
70+
pub struct IdPool {
71+
map: BitmapVec,
72+
}
73+
74+
/// Indicates that an [`IdPool`] should change to a new target size.
75+
pub struct ReallocRequest {
76+
num_ids: usize,
77+
}
78+
79+
/// Contains a [`BitmapVec`] of a size suitable for reallocating [`IdPool`].
80+
pub struct PoolResizer {
81+
new: BitmapVec,
82+
}
83+
84+
impl ReallocRequest {
85+
/// Allocates a new backing [`BitmapVec`] for [`IdPool`].
86+
///
87+
/// This method only prepares reallocation and does not complete it.
88+
/// Reallocation will complete after passing the [`PoolResizer`] to the
89+
/// [`IdPool::grow`] or [`IdPool::shrink`] operation, which will check
90+
/// that reallocation still makes sense.
91+
pub fn realloc(&self, flags: Flags) -> Result<PoolResizer, AllocError> {
92+
let new = BitmapVec::new(self.num_ids, flags)?;
93+
Ok(PoolResizer { new })
94+
}
95+
}
96+
97+
impl IdPool {
98+
/// Constructs a new [`IdPool`].
99+
///
100+
/// A capacity below [`BITS_PER_LONG`] is adjusted to
101+
/// [`BITS_PER_LONG`].
102+
///
103+
/// [`BITS_PER_LONG`]: srctree/include/asm-generic/bitsperlong.h
104+
#[inline]
105+
pub fn new(num_ids: usize, flags: Flags) -> Result<Self, AllocError> {
106+
let num_ids = core::cmp::max(num_ids, BITS_PER_LONG);
107+
let map = BitmapVec::new(num_ids, flags)?;
108+
Ok(Self { map })
109+
}
110+
111+
/// Returns how many IDs this pool can currently have.
112+
#[inline]
113+
pub fn capacity(&self) -> usize {
114+
self.map.len()
115+
}
116+
117+
/// Returns a [`ReallocRequest`] if the [`IdPool`] can be shrunk, [`None`] otherwise.
118+
///
119+
/// The capacity of an [`IdPool`] cannot be shrunk below [`BITS_PER_LONG`].
120+
///
121+
/// [`BITS_PER_LONG`]: srctree/include/asm-generic/bitsperlong.h
122+
///
123+
/// # Examples
124+
///
125+
/// ```
126+
/// use kernel::alloc::{AllocError, flags::GFP_KERNEL};
127+
/// use kernel::id_pool::{ReallocRequest, IdPool};
128+
///
129+
/// let mut pool = IdPool::new(1024, GFP_KERNEL)?;
130+
/// let alloc_request = pool.shrink_request().ok_or(AllocError)?;
131+
/// let resizer = alloc_request.realloc(GFP_KERNEL)?;
132+
/// pool.shrink(resizer);
133+
/// assert_eq!(pool.capacity(), kernel::bindings::BITS_PER_LONG as usize);
134+
/// # Ok::<(), AllocError>(())
135+
/// ```
136+
#[inline]
137+
pub fn shrink_request(&self) -> Option<ReallocRequest> {
138+
let cap = self.capacity();
139+
// Shrinking below [`BITS_PER_LONG`] is never possible.
140+
if cap <= BITS_PER_LONG {
141+
return None;
142+
}
143+
// Determine if the bitmap can shrink based on the position of
144+
// its last set bit. If the bit is within the first quarter of
145+
// the bitmap then shrinking is possible. In this case, the
146+
// bitmap should shrink to half its current size.
147+
let Some(bit) = self.map.last_bit() else {
148+
return Some(ReallocRequest {
149+
num_ids: BITS_PER_LONG,
150+
});
151+
};
152+
if bit >= (cap / 4) {
153+
return None;
154+
}
155+
let num_ids = usize::max(BITS_PER_LONG, cap / 2);
156+
Some(ReallocRequest { num_ids })
157+
}
158+
159+
/// Shrinks pool by using a new [`BitmapVec`], if still possible.
160+
#[inline]
161+
pub fn shrink(&mut self, mut resizer: PoolResizer) {
162+
// Between request to shrink that led to allocation of `resizer` and now,
163+
// bits may have changed.
164+
// Verify that shrinking is still possible. In case shrinking to
165+
// the size of `resizer` is no longer possible, do nothing,
166+
// drop `resizer` and move on.
167+
let Some(updated) = self.shrink_request() else {
168+
return;
169+
};
170+
if updated.num_ids > resizer.new.len() {
171+
return;
172+
}
173+
174+
resizer.new.copy_and_extend(&self.map);
175+
self.map = resizer.new;
176+
}
177+
178+
/// Returns a [`ReallocRequest`] for growing this [`IdPool`], if possible.
179+
///
180+
/// The capacity of an [`IdPool`] cannot be grown above [`i32::MAX`].
181+
#[inline]
182+
pub fn grow_request(&self) -> Option<ReallocRequest> {
183+
let num_ids = self.capacity() * 2;
184+
if num_ids > i32::MAX.try_into().unwrap() {
185+
return None;
186+
}
187+
Some(ReallocRequest { num_ids })
188+
}
189+
190+
/// Grows pool by using a new [`BitmapVec`], if still necessary.
191+
///
192+
/// The `resizer` arguments has to be obtained by calling [`Self::grow_request`]
193+
/// on this object and performing a [`ReallocRequest::realloc`].
194+
#[inline]
195+
pub fn grow(&mut self, mut resizer: PoolResizer) {
196+
// Between request to grow that led to allocation of `resizer` and now,
197+
// another thread may have already grown the capacity.
198+
// In this case, do nothing, drop `resizer` and move on.
199+
if resizer.new.len() <= self.capacity() {
200+
return;
201+
}
202+
203+
resizer.new.copy_and_extend(&self.map);
204+
self.map = resizer.new;
205+
}
206+
207+
/// Acquires a new ID by finding and setting the next zero bit in the
208+
/// bitmap.
209+
///
210+
/// Upon success, returns its index. Otherwise, returns [`None`]
211+
/// to indicate that a [`Self::grow_request`] is needed.
212+
#[inline]
213+
pub fn acquire_next_id(&mut self, offset: usize) -> Option<usize> {
214+
let next_zero_bit = self.map.next_zero_bit(offset);
215+
if let Some(nr) = next_zero_bit {
216+
self.map.set_bit(nr);
217+
}
218+
next_zero_bit
219+
}
220+
221+
/// Releases an ID.
222+
#[inline]
223+
pub fn release_id(&mut self, id: usize) {
224+
self.map.clear_bit(id);
225+
}
226+
}

rust/kernel/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ pub mod faux;
9090
pub mod firmware;
9191
pub mod fmt;
9292
pub mod fs;
93+
pub mod id_pool;
9394
pub mod init;
9495
pub mod io;
9596
pub mod ioctl;

0 commit comments

Comments
 (0)