mmap_allocator/
mmap_allocator.rs

1// Copyright 2020,2021 Shin Yoshida
2//
3// "LGPL-3.0-or-later OR Apache-2.0"
4//
5// This is part of rust-mmap-allocator
6//
7//  rust-mmap-allocator is free software: you can redistribute it and/or modify
8//  it under the terms of the GNU General Public License as published by
9//  the Free Software Foundation, either version 3 of the License, or
10//  (at your option) any later version.
11//
12//  rust-mmap-allocator is distributed in the hope that it will be useful,
13//  but WITHOUT ANY WARRANTY; without even the implied warranty of
14//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15//  GNU General Public License for more details.
16//
17//  You should have received a copy of the GNU General Public License
18//  along with rust-mmap-allocator.  If not, see <http://www.gnu.org/licenses/>.
19//
20// Licensed under the Apache License, Version 2.0 (the "License");
21// you may not use this file except in compliance with the License.
22// You may obtain a copy of the License at
23//
24//     http://www.apache.org/licenses/LICENSE-2.0
25//
26// Unless required by applicable law or agreed to in writing, software
27// distributed under the License is distributed on an "AS IS" BASIS,
28// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29// See the License for the specific language governing permissions and
30// limitations under the License.
31
32use libc::{off_t, size_t};
33use std::alloc::{GlobalAlloc, Layout};
34use std::cell::Cell;
35use std::os::raw::{c_int, c_long, c_void};
36use std::ptr;
37
38/// Implementation of std::alloc::GlobalAlloc whose backend is mmap(2)
39#[derive(Debug, Clone, Copy)]
40pub struct MmapAllocator;
41
42impl Default for MmapAllocator {
43    #[inline]
44    fn default() -> Self {
45        Self
46    }
47}
48
49impl MmapAllocator {
50    /// Creates a new instance.
51    #[inline]
52    pub const fn new() -> Self {
53        Self
54    }
55}
56
57/// # Portability
58///
59/// alloc() calls mmap() with flag MAP_ANONYMOUS.
60/// Many systems support the flag, however, it is not specified in POSIX.
61///
62/// # Safety
63///
64/// All functions are thread safe.
65///
66/// # Error
67///
68/// Each function don't cause panic but set OS errno on error.
69///
70/// Note that it is not an error to deallocate pointer which is not allocated.
71/// This is the spec of munmap(2). See `man 2 munmap` for details.
72unsafe impl GlobalAlloc for MmapAllocator {
73    /// # Panics
74    ///
75    /// This method may panic if the align of `layout` is greater than the kernel page align.
76    /// (Basically, kernel page align is always greater than the align of `layout` that rust
77    /// generates unless the programer dares to build such a `layout` on purpose.)
78    #[inline]
79    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
80        const ADDR: *mut c_void = ptr::null_mut::<c_void>();
81        let length = layout.size() as size_t;
82        const PROT: c_int = libc::PROT_READ | libc::PROT_WRITE;
83
84        // No backend file.
85        // MAP_UNINITIALIZED is not very common.
86        // To make this module portable, don't use it.
87        const FLAGS: c_int = libc::MAP_PRIVATE | libc::MAP_ANONYMOUS;
88        const FD: c_int = -1; // Should be -1 if flags includes MAP_ANONYMOUS. See `man 2 mmap`
89        const OFFSET: off_t = 0; // Should be 0 if flags includes MAP_ANONYMOUS. See `man 2 mmap`
90
91        match mmap(ADDR, length, PROT, FLAGS, FD, OFFSET) {
92            libc::MAP_FAILED => ptr::null_mut::<u8>(),
93            ret => {
94                let ptr = ret as usize;
95                assert_eq!(0, ptr % layout.align());
96                ret as *mut u8
97            }
98        }
99    }
100
101    #[inline]
102    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
103        let addr = ptr as *mut c_void;
104        let length = layout.size() as size_t;
105
106        munmap(addr, length);
107    }
108
109    /// # Panics
110    ///
111    /// This method can panic if the align of `layout` is greater than the kernel page align.
112    #[inline]
113    unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
114        // alloc() calls mmap() with the flags which always fills the memory 0.
115        self.alloc(layout)
116    }
117
118    #[cfg(linux)]
119    #[inline]
120    unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
121        let old_address = ptr as *mut c_void;
122        let old_size = layout.size() as size_t;
123        let new_size = new_size as size_t;
124        let FLAGS = libc::MREMAP_MAYMOVE;
125
126        match mremap(old_address, old_size, new_size, FLAGS) {
127            libc::MAP_FAILED => ptr::null_mut::<u8>(),
128            ret => ret as *mut u8,
129        }
130    }
131}
132
133extern "C" {
134    fn mmap(
135        addr: *mut c_void,
136        length: size_t,
137        prot: c_int,
138        flags: c_int,
139        fd: c_int,
140        offset: off_t,
141    ) -> *mut c_void;
142
143    fn munmap(addr: *mut c_void, length: size_t);
144
145    #[cfg(linux)]
146    fn mremap(
147        old_address: *mut c_void,
148        old_size: size_t,
149        new_size: size_t,
150        flags: c_int,
151    ) -> *mut c_void;
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157    use std::mem;
158    use std::ptr;
159
160    const ENOERR: i32 = 0;
161
162    fn clear_errno() {
163        unsafe { *libc::__errno_location() = 0 }
164    }
165
166    fn errno() -> i32 {
167        unsafe { *libc::__errno_location() }
168    }
169
170    #[test]
171    fn default() {
172        let _alloc = MmapAllocator::default();
173    }
174
175    #[test]
176    fn allocate() {
177        unsafe {
178            type T = i64;
179            let alloc = MmapAllocator::default();
180
181            let layout = Layout::new::<i64>();
182            let ptr = alloc.alloc(layout) as *mut T;
183            assert_ne!(std::ptr::null(), ptr);
184
185            *ptr = 84;
186            assert_eq!(84, *ptr);
187
188            *ptr = *ptr * -2;
189            assert_eq!(-168, *ptr);
190
191            alloc.dealloc(ptr as *mut u8, layout)
192        }
193    }
194
195    #[test]
196    fn allocate_too_large() {
197        unsafe {
198            clear_errno();
199
200            type T = String;
201            let alloc = MmapAllocator::default();
202
203            let align = mem::align_of::<T>();
204            let size = std::usize::MAX - mem::size_of::<T>();
205            let layout = Layout::from_size_align(size, align).unwrap();
206
207            assert_eq!(ptr::null(), alloc.alloc(layout));
208            assert_ne!(ENOERR, errno());
209        }
210    }
211
212    #[test]
213    fn allocate_zero_size() {
214        unsafe {
215            clear_errno();
216
217            type T = String;
218            let alloc = MmapAllocator::default();
219
220            let align = mem::align_of::<T>();
221            let size = 0;
222            let layout = Layout::from_size_align(size, align).unwrap();
223
224            assert_eq!(ptr::null(), alloc.alloc(layout));
225            assert_ne!(ENOERR, errno());
226        }
227    }
228
229    #[test]
230    fn alloc_zeroed() {
231        unsafe {
232            type T = [u8; 1025];
233            let alloc = MmapAllocator::default();
234
235            let layout = Layout::new::<T>();
236            let ptr = alloc.alloc_zeroed(layout) as *mut T;
237            let s: &[u8] = &*ptr;
238
239            for u in s {
240                assert_eq!(0, *u);
241            }
242
243            alloc.dealloc(ptr as *mut u8, layout);
244        }
245    }
246
247    #[test]
248    fn realloc() {
249        unsafe {
250            type T = [u8; 1025];
251            let alloc = MmapAllocator::default();
252
253            let layout = Layout::new::<T>();
254            let ptr = alloc.alloc(layout) as *mut T;
255
256            let ts = &mut *ptr;
257            for t in ts.iter_mut() {
258                *t = 1;
259            }
260
261            type U = (T, T);
262
263            let new_size = mem::size_of::<U>();
264            let ptr = alloc.realloc(ptr as *mut u8, layout, new_size) as *mut T;
265            let layout = Layout::from_size_align(new_size, layout.align()).unwrap();
266
267            let ts = &mut *ptr;
268            for t in ts.iter_mut() {
269                assert_eq!(1, *t);
270                *t = 2;
271            }
272
273            let new_size = mem::size_of::<u8>();
274            let ptr = alloc.realloc(ptr as *mut u8, layout, new_size);
275            let layout = Layout::from_size_align(new_size, layout.align()).unwrap();
276
277            assert_eq!(2, *ptr);
278
279            alloc.dealloc(ptr, layout);
280        }
281    }
282
283    #[test]
284    fn realloc_too_large() {
285        unsafe {
286            type T = [u8; 1025];
287            let alloc = MmapAllocator::default();
288
289            let layout = Layout::new::<T>();
290            let ptr = alloc.alloc(layout) as *mut T;
291
292            let ts = &mut *ptr;
293            for t in ts.iter_mut() {
294                *t = 1;
295            }
296
297            let new_size = std::usize::MAX - mem::size_of::<T>();
298            let new_ptr = alloc.realloc(ptr as *mut u8, layout, new_size);
299
300            assert!(new_ptr.is_null());
301            assert_ne!(ENOERR, errno());
302
303            for t in ts.iter() {
304                assert_eq!(1, *t);
305            }
306
307            alloc.dealloc(ptr as *mut u8, layout);
308        }
309    }
310}
311
312thread_local! {
313    static PAGE_SIZE: Cell<usize> = Cell::new(0);
314}
315
316/// Returns OS Page Size.
317///
318/// See crate document for details.
319#[inline]
320pub fn page_size() -> usize {
321    PAGE_SIZE.with(|s| match s.get() {
322        0 => {
323            let ret = unsafe { sysconf(libc::_SC_PAGE_SIZE) as usize };
324            s.set(ret);
325            ret
326        }
327        ret => ret,
328    })
329}
330
331extern "C" {
332    fn sysconf(name: c_int) -> c_long;
333}