xref: /linux/rust/kernel/alloc/allocator.rs (revision 8804d970fab45726b3c7cd7f240b31122aa94219)
1 // SPDX-License-Identifier: GPL-2.0
2 
3 //! Allocator support.
4 //!
5 //! Documentation for the kernel's memory allocators can found in the "Memory Allocation Guide"
6 //! linked below. For instance, this includes the concept of "get free page" (GFP) flags and the
7 //! typical application of the different kernel allocators.
8 //!
9 //! Reference: <https://docs.kernel.org/core-api/memory-allocation.html>
10 
11 use super::Flags;
12 use core::alloc::Layout;
13 use core::ptr;
14 use core::ptr::NonNull;
15 
16 use crate::alloc::{AllocError, Allocator, NumaNode};
17 use crate::bindings;
18 use crate::page;
19 
20 const ARCH_KMALLOC_MINALIGN: usize = bindings::ARCH_KMALLOC_MINALIGN;
21 
22 mod iter;
23 pub use self::iter::VmallocPageIter;
24 
25 /// The contiguous kernel allocator.
26 ///
27 /// `Kmalloc` is typically used for physically contiguous allocations up to page size, but also
28 /// supports larger allocations up to `bindings::KMALLOC_MAX_SIZE`, which is hardware specific.
29 ///
30 /// For more details see [self].
31 pub struct Kmalloc;
32 
33 /// The virtually contiguous kernel allocator.
34 ///
35 /// `Vmalloc` allocates pages from the page level allocator and maps them into the contiguous kernel
36 /// virtual space. It is typically used for large allocations. The memory allocated with this
37 /// allocator is not physically contiguous.
38 ///
39 /// For more details see [self].
40 pub struct Vmalloc;
41 
42 /// The kvmalloc kernel allocator.
43 ///
44 /// `KVmalloc` attempts to allocate memory with `Kmalloc` first, but falls back to `Vmalloc` upon
45 /// failure. This allocator is typically used when the size for the requested allocation is not
46 /// known and may exceed the capabilities of `Kmalloc`.
47 ///
48 /// For more details see [self].
49 pub struct KVmalloc;
50 
51 /// # Invariants
52 ///
53 /// One of the following: `krealloc_node_align`, `vrealloc_node_align`, `kvrealloc_node_align`.
54 struct ReallocFunc(
55     unsafe extern "C" fn(
56         *const crate::ffi::c_void,
57         usize,
58         crate::ffi::c_ulong,
59         u32,
60         crate::ffi::c_int,
61     ) -> *mut crate::ffi::c_void,
62 );
63 
64 impl ReallocFunc {
65     // INVARIANT: `krealloc_node_align` satisfies the type invariants.
66     const KREALLOC: Self = Self(bindings::krealloc_node_align);
67 
68     // INVARIANT: `vrealloc_node_align` satisfies the type invariants.
69     const VREALLOC: Self = Self(bindings::vrealloc_node_align);
70 
71     // INVARIANT: `kvrealloc_node_align` satisfies the type invariants.
72     const KVREALLOC: Self = Self(bindings::kvrealloc_node_align);
73 
74     /// # Safety
75     ///
76     /// This method has the same safety requirements as [`Allocator::realloc`].
77     ///
78     /// # Guarantees
79     ///
80     /// This method has the same guarantees as `Allocator::realloc`. Additionally
81     /// - it accepts any pointer to a valid memory allocation allocated by this function.
82     /// - memory allocated by this function remains valid until it is passed to this function.
83     #[inline]
call( &self, ptr: Option<NonNull<u8>>, layout: Layout, old_layout: Layout, flags: Flags, nid: NumaNode, ) -> Result<NonNull<[u8]>, AllocError>84     unsafe fn call(
85         &self,
86         ptr: Option<NonNull<u8>>,
87         layout: Layout,
88         old_layout: Layout,
89         flags: Flags,
90         nid: NumaNode,
91     ) -> Result<NonNull<[u8]>, AllocError> {
92         let size = layout.size();
93         let ptr = match ptr {
94             Some(ptr) => {
95                 if old_layout.size() == 0 {
96                     ptr::null()
97                 } else {
98                     ptr.as_ptr()
99                 }
100             }
101             None => ptr::null(),
102         };
103 
104         // SAFETY:
105         // - `self.0` is one of `krealloc`, `vrealloc`, `kvrealloc` and thus only requires that
106         //   `ptr` is NULL or valid.
107         // - `ptr` is either NULL or valid by the safety requirements of this function.
108         //
109         // GUARANTEE:
110         // - `self.0` is one of `krealloc`, `vrealloc`, `kvrealloc`.
111         // - Those functions provide the guarantees of this function.
112         let raw_ptr = unsafe {
113             // If `size == 0` and `ptr != NULL` the memory behind the pointer is freed.
114             self.0(ptr.cast(), size, layout.align(), flags.0, nid.0).cast()
115         };
116 
117         let ptr = if size == 0 {
118             crate::alloc::dangling_from_layout(layout)
119         } else {
120             NonNull::new(raw_ptr).ok_or(AllocError)?
121         };
122 
123         Ok(NonNull::slice_from_raw_parts(ptr, size))
124     }
125 }
126 
127 impl Kmalloc {
128     /// Returns a [`Layout`] that makes [`Kmalloc`] fulfill the requested size and alignment of
129     /// `layout`.
aligned_layout(layout: Layout) -> Layout130     pub fn aligned_layout(layout: Layout) -> Layout {
131         // Note that `layout.size()` (after padding) is guaranteed to be a multiple of
132         // `layout.align()` which together with the slab guarantees means that `Kmalloc` will return
133         // a properly aligned object (see comments in `kmalloc()` for more information).
134         layout.pad_to_align()
135     }
136 }
137 
138 // SAFETY: `realloc` delegates to `ReallocFunc::call`, which guarantees that
139 // - memory remains valid until it is explicitly freed,
140 // - passing a pointer to a valid memory allocation is OK,
141 // - `realloc` satisfies the guarantees, since `ReallocFunc::call` has the same.
142 unsafe impl Allocator for Kmalloc {
143     const MIN_ALIGN: usize = ARCH_KMALLOC_MINALIGN;
144 
145     #[inline]
realloc( ptr: Option<NonNull<u8>>, layout: Layout, old_layout: Layout, flags: Flags, nid: NumaNode, ) -> Result<NonNull<[u8]>, AllocError>146     unsafe fn realloc(
147         ptr: Option<NonNull<u8>>,
148         layout: Layout,
149         old_layout: Layout,
150         flags: Flags,
151         nid: NumaNode,
152     ) -> Result<NonNull<[u8]>, AllocError> {
153         let layout = Kmalloc::aligned_layout(layout);
154 
155         // SAFETY: `ReallocFunc::call` has the same safety requirements as `Allocator::realloc`.
156         unsafe { ReallocFunc::KREALLOC.call(ptr, layout, old_layout, flags, nid) }
157     }
158 }
159 
160 impl Vmalloc {
161     /// Convert a pointer to a [`Vmalloc`] allocation to a [`page::BorrowedPage`].
162     ///
163     /// # Examples
164     ///
165     /// ```
166     /// # use core::ptr::{NonNull, from_mut};
167     /// # use kernel::{page, prelude::*};
168     /// use kernel::alloc::allocator::Vmalloc;
169     ///
170     /// let mut vbox = VBox::<[u8; page::PAGE_SIZE]>::new_uninit(GFP_KERNEL)?;
171     ///
172     /// {
173     ///     // SAFETY: By the type invariant of `Box` the inner pointer of `vbox` is non-null.
174     ///     let ptr = unsafe { NonNull::new_unchecked(from_mut(&mut *vbox)) };
175     ///
176     ///     // SAFETY:
177     ///     // `ptr` is a valid pointer to a `Vmalloc` allocation.
178     ///     // `ptr` is valid for the entire lifetime of `page`.
179     ///     let page = unsafe { Vmalloc::to_page(ptr.cast()) };
180     ///
181     ///     // SAFETY: There is no concurrent read or write to the same page.
182     ///     unsafe { page.fill_zero_raw(0, page::PAGE_SIZE)? };
183     /// }
184     /// # Ok::<(), Error>(())
185     /// ```
186     ///
187     /// # Safety
188     ///
189     /// - `ptr` must be a valid pointer to a [`Vmalloc`] allocation.
190     /// - `ptr` must remain valid for the entire duration of `'a`.
to_page<'a>(ptr: NonNull<u8>) -> page::BorrowedPage<'a>191     pub unsafe fn to_page<'a>(ptr: NonNull<u8>) -> page::BorrowedPage<'a> {
192         // SAFETY: `ptr` is a valid pointer to `Vmalloc` memory.
193         let page = unsafe { bindings::vmalloc_to_page(ptr.as_ptr().cast()) };
194 
195         // SAFETY: `vmalloc_to_page` returns a valid pointer to a `struct page` for a valid pointer
196         // to `Vmalloc` memory.
197         let page = unsafe { NonNull::new_unchecked(page) };
198 
199         // SAFETY:
200         // - `page` is a valid pointer to a `struct page`, given that by the safety requirements of
201         //   this function `ptr` is a valid pointer to a `Vmalloc` allocation.
202         // - By the safety requirements of this function `ptr` is valid for the entire lifetime of
203         //   `'a`.
204         unsafe { page::BorrowedPage::from_raw(page) }
205     }
206 }
207 
208 // SAFETY: `realloc` delegates to `ReallocFunc::call`, which guarantees that
209 // - memory remains valid until it is explicitly freed,
210 // - passing a pointer to a valid memory allocation is OK,
211 // - `realloc` satisfies the guarantees, since `ReallocFunc::call` has the same.
212 unsafe impl Allocator for Vmalloc {
213     const MIN_ALIGN: usize = kernel::page::PAGE_SIZE;
214 
215     #[inline]
realloc( ptr: Option<NonNull<u8>>, layout: Layout, old_layout: Layout, flags: Flags, nid: NumaNode, ) -> Result<NonNull<[u8]>, AllocError>216     unsafe fn realloc(
217         ptr: Option<NonNull<u8>>,
218         layout: Layout,
219         old_layout: Layout,
220         flags: Flags,
221         nid: NumaNode,
222     ) -> Result<NonNull<[u8]>, AllocError> {
223         // SAFETY: If not `None`, `ptr` is guaranteed to point to valid memory, which was previously
224         // allocated with this `Allocator`.
225         unsafe { ReallocFunc::VREALLOC.call(ptr, layout, old_layout, flags, nid) }
226     }
227 }
228 
229 // SAFETY: `realloc` delegates to `ReallocFunc::call`, which guarantees that
230 // - memory remains valid until it is explicitly freed,
231 // - passing a pointer to a valid memory allocation is OK,
232 // - `realloc` satisfies the guarantees, since `ReallocFunc::call` has the same.
233 unsafe impl Allocator for KVmalloc {
234     const MIN_ALIGN: usize = ARCH_KMALLOC_MINALIGN;
235 
236     #[inline]
realloc( ptr: Option<NonNull<u8>>, layout: Layout, old_layout: Layout, flags: Flags, nid: NumaNode, ) -> Result<NonNull<[u8]>, AllocError>237     unsafe fn realloc(
238         ptr: Option<NonNull<u8>>,
239         layout: Layout,
240         old_layout: Layout,
241         flags: Flags,
242         nid: NumaNode,
243     ) -> Result<NonNull<[u8]>, AllocError> {
244         // `KVmalloc` may use the `Kmalloc` backend, hence we have to enforce a `Kmalloc`
245         // compatible layout.
246         let layout = Kmalloc::aligned_layout(layout);
247 
248         // SAFETY: If not `None`, `ptr` is guaranteed to point to valid memory, which was previously
249         // allocated with this `Allocator`.
250         unsafe { ReallocFunc::KVREALLOC.call(ptr, layout, old_layout, flags, nid) }
251     }
252 }
253 
254 #[macros::kunit_tests(rust_allocator)]
255 mod tests {
256     use super::*;
257     use core::mem::MaybeUninit;
258     use kernel::prelude::*;
259 
260     #[test]
test_alignment() -> Result261     fn test_alignment() -> Result {
262         const TEST_SIZE: usize = 1024;
263         const TEST_LARGE_ALIGN_SIZE: usize = kernel::page::PAGE_SIZE * 4;
264 
265         // These two structs are used to test allocating aligned memory.
266         // they don't need to be accessed, so they're marked as dead_code.
267         #[expect(dead_code)]
268         #[repr(align(128))]
269         struct Blob([u8; TEST_SIZE]);
270         #[expect(dead_code)]
271         #[repr(align(8192))]
272         struct LargeAlignBlob([u8; TEST_LARGE_ALIGN_SIZE]);
273 
274         struct TestAlign<T, A: Allocator>(Box<MaybeUninit<T>, A>);
275         impl<T, A: Allocator> TestAlign<T, A> {
276             fn new() -> Result<Self> {
277                 Ok(Self(Box::<_, A>::new_uninit(GFP_KERNEL)?))
278             }
279 
280             fn is_aligned_to(&self, align: usize) -> bool {
281                 assert!(align.is_power_of_two());
282 
283                 let addr = self.0.as_ptr() as usize;
284                 addr & (align - 1) == 0
285             }
286         }
287 
288         let ta = TestAlign::<Blob, Kmalloc>::new()?;
289         assert!(ta.is_aligned_to(128));
290 
291         let ta = TestAlign::<LargeAlignBlob, Kmalloc>::new()?;
292         assert!(ta.is_aligned_to(8192));
293 
294         let ta = TestAlign::<Blob, Vmalloc>::new()?;
295         assert!(ta.is_aligned_to(128));
296 
297         let ta = TestAlign::<LargeAlignBlob, Vmalloc>::new()?;
298         assert!(ta.is_aligned_to(8192));
299 
300         let ta = TestAlign::<Blob, KVmalloc>::new()?;
301         assert!(ta.is_aligned_to(128));
302 
303         let ta = TestAlign::<LargeAlignBlob, KVmalloc>::new()?;
304         assert!(ta.is_aligned_to(8192));
305 
306         Ok(())
307     }
308 }
309