@@ -40,14 +40,25 @@ pub enum Error {
4040 /// The `mmap` call returned an error.
4141 #[ error( "{0}" ) ]
4242 Mmap ( io:: Error ) ,
43+ /// Invalid aligment specified.
44+ #[ error( "The minimum alignment is not a power of two and at least the system page size" ) ]
45+ InvalidAlignment ,
4346}
4447
4548pub type Result < T > = result:: Result < T , Error > ;
4649
50+ /// Retrieve the system page size from sysconf
51+ fn system_page_size ( ) -> usize {
52+ // SAFETY: Safe because this call just returns the page size and doesn't have any side
53+ // effects.
54+ unsafe { libc:: sysconf ( libc:: _SC_PAGESIZE) as usize }
55+ }
56+
4757/// A factory struct to build `MmapRegion` objects.
4858#[ derive( Debug ) ]
4959pub struct MmapRegionBuilder < B = ( ) > {
5060 size : usize ,
61+ alignment : usize ,
5162 prot : i32 ,
5263 flags : i32 ,
5364 file_offset : Option < FileOffset > ,
@@ -73,6 +84,7 @@ impl<B: Bitmap> MmapRegionBuilder<B> {
7384 pub fn new_with_bitmap ( size : usize , bitmap : B ) -> Self {
7485 MmapRegionBuilder {
7586 size,
87+ alignment : system_page_size ( ) ,
7688 prot : 0 ,
7789 flags : libc:: MAP_ANONYMOUS | libc:: MAP_PRIVATE ,
7890 file_offset : None ,
@@ -94,6 +106,13 @@ impl<B: Bitmap> MmapRegionBuilder<B> {
94106 self
95107 }
96108
109+ /// Create the `MmapRegion` object with the specified minimum alignment.
110+ /// `min_align` must be a power of two and at least the system page size.
111+ pub fn with_minimum_alignment ( mut self , min_align : usize ) -> Self {
112+ self . alignment = min_align;
113+ self
114+ }
115+
97116 /// Create the `MmapRegion` object with the specified `file_offset`.
98117 pub fn with_file_offset ( mut self , file_offset : FileOffset ) -> Self {
99118 self . file_offset = Some ( file_offset) ;
@@ -119,6 +138,11 @@ impl<B: Bitmap> MmapRegionBuilder<B> {
119138
120139 /// Build the `MmapRegion` object.
121140 pub fn build ( self ) -> Result < MmapRegion < B > > {
141+ let page_size = system_page_size ( ) ;
142+ if !self . alignment . is_power_of_two ( ) || self . alignment < page_size {
143+ return Err ( Error :: InvalidAlignment ) ;
144+ }
145+
122146 if self . raw_ptr . is_some ( ) {
123147 return self . build_raw ( ) ;
124148 }
@@ -136,18 +160,74 @@ impl<B: Bitmap> MmapRegionBuilder<B> {
136160 } ;
137161
138162 #[ cfg( not( miri) ) ]
139- // SAFETY: This is safe because we're not allowing MAP_FIXED, and invalid parameters
140- // cannot break Rust safety guarantees (things may change if we're mapping /dev/mem or
141- // some wacky file).
142- let addr = unsafe {
143- libc:: mmap (
144- null_mut ( ) ,
145- self . size ,
146- self . prot ,
147- self . flags ,
148- fd,
149- offset as libc:: off_t ,
150- )
163+ let addr = {
164+ // To support alignment, first reserve a sufficiently large region of address space
165+ // using `MAP_NORESERVE`, and then find and place the real mapping at the aligned
166+ // sub-region contained within via `MAP_FIXED`. Then free the excess.
167+ let page_aligned_size = self . size . next_multiple_of ( page_size) ;
168+ let va_reserved_size = page_aligned_size + self . alignment ;
169+
170+ // SAFETY: Calling mmap with correct arguments to reserve virtual address space.
171+ let va_reserved_addr = unsafe {
172+ libc:: mmap (
173+ null_mut ( ) ,
174+ va_reserved_size,
175+ libc:: PROT_NONE ,
176+ libc:: MAP_PRIVATE | libc:: MAP_ANONYMOUS | libc:: MAP_NORESERVE ,
177+ -1 , // fd for MAP_ANONYMOUS
178+ 0 , // no offset
179+ )
180+ } ;
181+
182+ if va_reserved_addr == libc:: MAP_FAILED {
183+ return Err ( Error :: Mmap ( io:: Error :: last_os_error ( ) ) ) ;
184+ }
185+
186+ let va_reserved_addr = va_reserved_addr as usize ;
187+ let aligned_addr = va_reserved_addr. next_multiple_of ( self . alignment ) ;
188+
189+ // Need to free the extra address space later - calculate the ranges
190+ let va_prefix_size = aligned_addr - va_reserved_addr;
191+ let va_suffix_addr = ( aligned_addr + page_aligned_size) as * mut libc:: c_void ;
192+ let va_suffix_size = va_reserved_size - va_prefix_size - page_aligned_size;
193+ let va_reserved_addr = va_reserved_addr as * mut libc:: c_void ;
194+
195+ // Place the actual mapping within the reserved region.
196+ // SAFETY: This is safe because we're not allowing MAP_FIXED, and invalid parameters
197+ // cannot break Rust safety guarantees (things may change if we're mapping /dev/mem or
198+ // some wacky file).
199+ let addr = unsafe {
200+ libc:: mmap (
201+ aligned_addr as * mut libc:: c_void ,
202+ // This is safe even if `self.size != page_aligned_size`. The mapping
203+ // happens at the page-granularity, so it is still replaced.
204+ self . size ,
205+ self . prot ,
206+ self . flags | libc:: MAP_FIXED , // Legal since we already reserved VA space
207+ fd,
208+ offset as libc:: off_t ,
209+ )
210+ } ;
211+
212+ // Don't leak the VA.
213+ if addr == libc:: MAP_FAILED {
214+ let e = io:: Error :: last_os_error ( ) ;
215+ // SAFETY: Calling as documented.
216+ unsafe { libc:: munmap ( va_reserved_addr, va_reserved_size) } ;
217+ return Err ( Error :: Mmap ( e) ) ;
218+ }
219+
220+ if va_prefix_size > 0 {
221+ // SAFETY: Calling as documented.
222+ unsafe { libc:: munmap ( va_reserved_addr, va_prefix_size) } ;
223+ }
224+
225+ if va_suffix_size > 0 {
226+ // SAFETY: Calling as documented.
227+ unsafe { libc:: munmap ( va_suffix_addr, va_suffix_size) } ;
228+ }
229+
230+ addr
151231 } ;
152232
153233 #[ cfg( not( miri) ) ]
@@ -163,12 +243,15 @@ impl<B: Bitmap> MmapRegionBuilder<B> {
163243 // Miri does not support the mmap syscall, so we use rust's allocator for miri tests
164244 #[ cfg( miri) ]
165245 let addr = unsafe {
166- std:: alloc:: alloc_zeroed ( std:: alloc:: Layout :: from_size_align ( self . size , 8 ) . unwrap ( ) )
246+ std:: alloc:: alloc_zeroed (
247+ std:: alloc:: Layout :: from_size_align ( self . size , self . alignment ) . unwrap ( ) ,
248+ )
167249 } ;
168250
169251 Ok ( MmapRegion {
170252 addr : addr as * mut u8 ,
171253 size : self . size ,
254+ alignment : self . alignment ,
172255 bitmap : self . bitmap ,
173256 file_offset : self . file_offset ,
174257 prot : self . prot ,
@@ -179,19 +262,18 @@ impl<B: Bitmap> MmapRegionBuilder<B> {
179262 }
180263
181264 fn build_raw ( self ) -> Result < MmapRegion < B > > {
182- // SAFETY: Safe because this call just returns the page size and doesn't have any side
183- // effects.
184- let page_size = unsafe { libc:: sysconf ( libc:: _SC_PAGESIZE) } as usize ;
185265 let addr = self . raw_ptr . unwrap ( ) ;
186266
187267 // Check that the pointer to the mapping is page-aligned.
188- if ( addr as usize ) & ( page_size - 1 ) != 0 {
268+ // `self.alignment` already validated by caller (`fn build`).
269+ if ( addr as usize ) & ( self . alignment - 1 ) != 0 {
189270 return Err ( Error :: InvalidPointer ) ;
190271 }
191272
192273 Ok ( MmapRegion {
193274 addr,
194275 size : self . size ,
276+ alignment : self . alignment ,
195277 bitmap : self . bitmap ,
196278 file_offset : self . file_offset ,
197279 prot : self . prot ,
@@ -215,6 +297,7 @@ impl<B: Bitmap> MmapRegionBuilder<B> {
215297pub struct MmapRegion < B = ( ) > {
216298 addr : * mut u8 ,
217299 size : usize ,
300+ alignment : usize ,
218301 bitmap : B ,
219302 file_offset : Option < FileOffset > ,
220303 prot : i32 ,
@@ -323,6 +406,11 @@ impl<B: Bitmap> MmapRegion<B> {
323406 self . size
324407 }
325408
409+ /// Returns the minimum alignment this region is guaranteed to have.
410+ pub fn minimum_alignment ( & self ) -> usize {
411+ self . alignment
412+ }
413+
326414 /// Returns information regarding the offset into the file backing this region (if any).
327415 pub fn file_offset ( & self ) -> Option < & FileOffset > {
328416 self . file_offset . as_ref ( )
@@ -425,7 +513,7 @@ impl<B> Drop for MmapRegion<B> {
425513 #[ cfg( miri) ]
426514 std:: alloc:: dealloc (
427515 self . addr ,
428- std:: alloc:: Layout :: from_size_align ( self . size , 8 ) . unwrap ( ) ,
516+ std:: alloc:: Layout :: from_size_align ( self . size , self . alignment ) . unwrap ( ) ,
429517 ) ;
430518 }
431519 }
@@ -617,6 +705,38 @@ mod tests {
617705 assert ! ( !r. owned( ) ) ;
618706 }
619707
708+ #[ test]
709+ fn test_mmap_alignment ( ) {
710+ let page_size = system_page_size ( ) ;
711+ let region_size = page_size - 1 ;
712+
713+ let builder = MmapRegionBuilder :: new_with_bitmap ( region_size, ( ) )
714+ . with_minimum_alignment ( page_size + 1 ) ;
715+ matches ! ( builder. build( ) , Err ( Error :: InvalidAlignment ) ) ;
716+
717+ let builder = MmapRegionBuilder :: new_with_bitmap ( region_size, ( ) )
718+ . with_minimum_alignment ( page_size - 1 ) ;
719+ matches ! ( builder. build( ) , Err ( Error :: InvalidAlignment ) ) ;
720+
721+ let builder = MmapRegionBuilder :: new_with_bitmap ( region_size, ( ) )
722+ . with_minimum_alignment ( page_size * 3 ) ; // not a power of two
723+ matches ! ( builder. build( ) , Err ( Error :: InvalidAlignment ) ) ;
724+
725+ let align_1gb = 1 * 1024 * 1024 * 1024 ; // 1 GiB
726+ let builder =
727+ MmapRegionBuilder :: new_with_bitmap ( region_size, ( ) ) . with_minimum_alignment ( align_1gb) ;
728+ let mapping = builder. build ( ) . unwrap ( ) ;
729+ assert_eq ! ( mapping. minimum_alignment( ) , align_1gb) ;
730+ assert_eq ! ( ( mapping. as_ptr( ) as usize ) % align_1gb, 0 ) ;
731+
732+ let builder = MmapRegionBuilder :: new_with_bitmap ( region_size, ( ) ) ;
733+ let mapping = builder. build ( ) . unwrap ( ) ;
734+
735+ assert_eq ! ( mapping. minimum_alignment( ) , page_size) ;
736+ // Technically we can get lucky and have the alignment naturally fall on a 1 GiB boundary,
737+ // so we can't assert that we DON'T here.
738+ }
739+
620740 #[ test]
621741 #[ cfg( not( miri) ) ] // Miri cannot mmap files
622742 fn test_mmap_region_fds_overlap ( ) {
0 commit comments