@@ -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,17 @@ 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 ) -> Result < Self > {
112+ if !self . alignment . is_power_of_two ( ) || self . alignment < system_page_size ( ) {
113+ return Err ( Error :: InvalidAlignment ) ;
114+ }
115+
116+ self . alignment = min_align;
117+ Ok ( self )
118+ }
119+
97120 /// Create the `MmapRegion` object with the specified `file_offset`.
98121 pub fn with_file_offset ( mut self , file_offset : FileOffset ) -> Self {
99122 self . file_offset = Some ( file_offset) ;
@@ -136,18 +159,74 @@ impl<B: Bitmap> MmapRegionBuilder<B> {
136159 } ;
137160
138161 #[ 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- )
162+ let addr = {
163+ // To support alignment, first reserve a sufficiently large region of address space
164+ // using `MAP_NORESERVE`, and then find and place the real mapping at the aligned
165+ // sub-region contained within via `MAP_FIXED`. Then free the excess.
166+ let page_aligned_size = self . size . next_multiple_of ( system_page_size ( ) ) ;
167+ let va_reserved_size = page_aligned_size + self . alignment ;
168+
169+ // SAFETY: Calling mmap with correct arguments to reserve virtual address space.
170+ let va_reserved_addr = unsafe {
171+ libc:: mmap (
172+ null_mut ( ) ,
173+ va_reserved_size,
174+ libc:: PROT_NONE ,
175+ libc:: MAP_PRIVATE | libc:: MAP_ANONYMOUS | libc:: MAP_NORESERVE ,
176+ -1 , // fd for MAP_ANONYMOUS
177+ 0 , // no offset
178+ )
179+ } ;
180+
181+ if va_reserved_addr == libc:: MAP_FAILED {
182+ return Err ( Error :: Mmap ( io:: Error :: last_os_error ( ) ) ) ;
183+ }
184+
185+ let va_reserved_addr = va_reserved_addr as usize ;
186+ let aligned_addr = va_reserved_addr. next_multiple_of ( self . alignment ) ;
187+
188+ // Need to free the extra address space later - calculate the ranges
189+ let va_prefix_size = aligned_addr - va_reserved_addr;
190+ let va_suffix_addr = ( aligned_addr + page_aligned_size) as * mut libc:: c_void ;
191+ let va_suffix_size = va_reserved_size - va_prefix_size - page_aligned_size;
192+ let va_reserved_addr = va_reserved_addr as * mut libc:: c_void ;
193+
194+ // Place the actual mapping within the reserved region.
195+ // SAFETY: This is safe because we're not allowing MAP_FIXED, and invalid parameters
196+ // cannot break Rust safety guarantees (things may change if we're mapping /dev/mem or
197+ // some wacky file).
198+ let addr = unsafe {
199+ libc:: mmap (
200+ aligned_addr as * mut libc:: c_void ,
201+ // This is safe even if `self.size != page_aligned_size`. The mapping
202+ // happens at the page-granularity, so it is still replaced.
203+ self . size ,
204+ self . prot ,
205+ self . flags | libc:: MAP_FIXED , // Legal since we already reserved VA space
206+ fd,
207+ offset as libc:: off_t ,
208+ )
209+ } ;
210+
211+ // Don't leak the VA.
212+ if addr == libc:: MAP_FAILED {
213+ let e = io:: Error :: last_os_error ( ) ;
214+ // SAFETY: Calling as documented.
215+ unsafe { libc:: munmap ( va_reserved_addr, va_reserved_size) } ;
216+ return Err ( Error :: Mmap ( e) ) ;
217+ }
218+
219+ if va_prefix_size > 0 {
220+ // SAFETY: Calling as documented.
221+ unsafe { libc:: munmap ( va_reserved_addr, va_prefix_size) } ;
222+ }
223+
224+ if va_suffix_size > 0 {
225+ // SAFETY: Calling as documented.
226+ unsafe { libc:: munmap ( va_suffix_addr, va_suffix_size) } ;
227+ }
228+
229+ addr
151230 } ;
152231
153232 #[ cfg( not( miri) ) ]
@@ -163,12 +242,15 @@ impl<B: Bitmap> MmapRegionBuilder<B> {
163242 // Miri does not support the mmap syscall, so we use rust's allocator for miri tests
164243 #[ cfg( miri) ]
165244 let addr = unsafe {
166- std:: alloc:: alloc_zeroed ( std:: alloc:: Layout :: from_size_align ( self . size , 8 ) . unwrap ( ) )
245+ std:: alloc:: alloc_zeroed (
246+ std:: alloc:: Layout :: from_size_align ( self . size , self . alignment ) . unwrap ( ) ,
247+ )
167248 } ;
168249
169250 Ok ( MmapRegion {
170251 addr : addr as * mut u8 ,
171252 size : self . size ,
253+ alignment : self . alignment ,
172254 bitmap : self . bitmap ,
173255 file_offset : self . file_offset ,
174256 prot : self . prot ,
@@ -179,19 +261,17 @@ impl<B: Bitmap> MmapRegionBuilder<B> {
179261 }
180262
181263 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 ;
185264 let addr = self . raw_ptr . unwrap ( ) ;
186265
187266 // Check that the pointer to the mapping is page-aligned.
188- if ( addr as usize ) & ( page_size - 1 ) != 0 {
267+ if ( addr as usize ) & ( self . alignment - 1 ) != 0 {
189268 return Err ( Error :: InvalidPointer ) ;
190269 }
191270
192271 Ok ( MmapRegion {
193272 addr,
194273 size : self . size ,
274+ alignment : self . alignment ,
195275 bitmap : self . bitmap ,
196276 file_offset : self . file_offset ,
197277 prot : self . prot ,
@@ -215,6 +295,7 @@ impl<B: Bitmap> MmapRegionBuilder<B> {
215295pub struct MmapRegion < B = ( ) > {
216296 addr : * mut u8 ,
217297 size : usize ,
298+ alignment : usize ,
218299 bitmap : B ,
219300 file_offset : Option < FileOffset > ,
220301 prot : i32 ,
@@ -323,6 +404,11 @@ impl<B: Bitmap> MmapRegion<B> {
323404 self . size
324405 }
325406
407+ /// Returns the minimum alignment this region is guaranteed to have.
408+ pub fn minimum_alignment ( & self ) -> usize {
409+ self . alignment
410+ }
411+
326412 /// Returns information regarding the offset into the file backing this region (if any).
327413 pub fn file_offset ( & self ) -> Option < & FileOffset > {
328414 self . file_offset . as_ref ( )
@@ -425,7 +511,7 @@ impl<B> Drop for MmapRegion<B> {
425511 #[ cfg( miri) ]
426512 std:: alloc:: dealloc (
427513 self . addr ,
428- std:: alloc:: Layout :: from_size_align ( self . size , 8 ) . unwrap ( ) ,
514+ std:: alloc:: Layout :: from_size_align ( self . size , self . alignment ) . unwrap ( ) ,
429515 ) ;
430516 }
431517 }
@@ -617,6 +703,52 @@ mod tests {
617703 assert ! ( !r. owned( ) ) ;
618704 }
619705
706+ #[ test]
707+ fn test_mmap_alignment ( ) {
708+ let page_size = system_page_size ( ) ;
709+ let region_size = page_size - 1 ;
710+
711+ let builder = MmapRegionBuilder :: new_with_bitmap ( region_size, ( ) ) ;
712+ matches ! (
713+ builder. with_minimum_alignment( page_size + 1 ) ,
714+ Err ( Error :: InvalidAlignment )
715+ ) ;
716+
717+ let builder = MmapRegionBuilder :: new_with_bitmap ( region_size, ( ) ) ;
718+ matches ! (
719+ builder. with_minimum_alignment( page_size - 1 ) ,
720+ Err ( Error :: InvalidAlignment )
721+ ) ;
722+
723+ let builder = MmapRegionBuilder :: new_with_bitmap ( region_size, ( ) ) ;
724+ matches ! (
725+ builder. with_minimum_alignment( page_size * 3 ) ,
726+ Err ( Error :: InvalidAlignment )
727+ ) ;
728+
729+ let builder = MmapRegionBuilder :: new_with_bitmap ( region_size, ( ) ) ;
730+ let mapping = builder. build ( ) . unwrap ( ) ;
731+
732+ assert_eq ! ( mapping. minimum_alignment( ) , page_size) ;
733+ // Technically we can get lucky and have the alignment naturally fall on a 1 GiB boundary,
734+ // so we can't assert that we DON'T here.
735+ }
736+
737+ #[ test]
738+ #[ cfg( not( miri) ) ] // 1 GiB alignment is too big for Miri
739+ fn test_mmap_alignment_1gib ( ) {
740+ let page_size = system_page_size ( ) ;
741+ let region_size = page_size - 1 ;
742+
743+ let align_1gb = 1024 * 1024 * 1024 ; // 1 GiB
744+ let builder = MmapRegionBuilder :: new_with_bitmap ( region_size, ( ) )
745+ . with_minimum_alignment ( align_1gb)
746+ . unwrap ( ) ;
747+ let mapping = builder. build ( ) . unwrap ( ) ;
748+ assert_eq ! ( mapping. minimum_alignment( ) , align_1gb) ;
749+ assert_eq ! ( ( mapping. as_ptr( ) as usize ) % align_1gb, 0 ) ;
750+ }
751+
620752 #[ test]
621753 #[ cfg( not( miri) ) ] // Miri cannot mmap files
622754 fn test_mmap_region_fds_overlap ( ) {
0 commit comments