7
7
8
8
use std:: fs:: File ;
9
9
use std:: io:: SeekFrom ;
10
+ use std:: os:: fd:: AsRawFd ;
10
11
use std:: sync:: Arc ;
11
12
12
13
use kvm_bindings:: { KVM_MEM_LOG_DIRTY_PAGES , kvm_userspace_memory_region} ;
@@ -119,6 +120,7 @@ impl GuestMemoryRegionExt {
119
120
// file only drops any anonymous pages in range, but subsequent accesses would read
120
121
// whatever page is stored on the backing file. Mmapping anonymous pages ensures
121
122
// it's zeroed.
123
+ // Punching a hole doesn't work in this case as the file is read-only.
122
124
// SAFETY: The address and length are known to be valid.
123
125
let ret = unsafe {
124
126
libc:: mmap (
@@ -138,9 +140,33 @@ impl GuestMemoryRegionExt {
138
140
Ok ( len)
139
141
}
140
142
}
141
- // TODO: memfd doesn't actually work with madvise, but this is to keep the
142
- // previous behaviour
143
- ( None , _) | ( Some ( _) , _) => {
143
+ // If and only if we are using fd-backed memory (eg memfd for vhost-user), we have a
144
+ // file descriptor and it's mapped shared.
145
+ ( Some ( file_offset) , flags) if flags & libc:: MAP_SHARED != 0 => {
146
+ // Fallocate to punch a hole in the region to free memory pages.
147
+ // In case of a FD mmapped as shared (eg memfd), MADV_DONTNEED doesn't work
148
+ // and we can't just mmap a zero page on top of it as that doesn't actually
149
+ // change the underlying shared FD, but we can punch a hole into it.
150
+ // SAFETY: The address and length are known to be valid.
151
+ let ret = unsafe {
152
+ libc:: fallocate64 (
153
+ file_offset. file ( ) . as_raw_fd ( ) ,
154
+ libc:: FALLOC_FL_PUNCH_HOLE | libc:: FALLOC_FL_KEEP_SIZE ,
155
+ i64:: try_from ( file_offset. start ( ) + caddr. 0 ) . unwrap ( ) ,
156
+ len. try_into ( ) . unwrap ( ) ,
157
+ )
158
+ } ;
159
+ if ret < 0 {
160
+ let os_error = std:: io:: Error :: last_os_error ( ) ;
161
+ error ! ( "discard_range: fallocate64 failed: {:?}" , os_error) ;
162
+ Err ( GuestMemoryError :: IOError ( os_error) )
163
+ } else {
164
+ Ok ( len)
165
+ }
166
+ }
167
+ // Either it's mapped PRIVATE or SHARED
168
+ ( Some ( _) , _) => unreachable ! ( ) ,
169
+ ( None , _) => {
144
170
// Madvise the region in order to mark it as not used.
145
171
// SAFETY: The address and length are known to be valid.
146
172
let ret = unsafe { libc:: madvise ( phys_address. cast ( ) , len, libc:: MADV_DONTNEED ) } ;
@@ -1044,4 +1070,49 @@ mod tests {
1044
1070
GuestMemoryError :: IOError ( _)
1045
1071
) ;
1046
1072
}
1073
+
1074
+ #[ test]
1075
+ fn test_discard_range_on_memfd ( ) {
1076
+ let page_size: usize = 0x1000 ;
1077
+ let mem = into_region_ext (
1078
+ memfd_backed (
1079
+ & [ ( GuestAddress ( 0 ) , 2 * page_size) ] ,
1080
+ false ,
1081
+ HugePageConfig :: None ,
1082
+ )
1083
+ . unwrap ( ) ,
1084
+ ) ;
1085
+
1086
+ // Fill the memory with ones.
1087
+ let ones = vec ! [ 1u8 ; 2 * page_size] ;
1088
+ mem. write ( & ones[ ..] , GuestAddress ( 0 ) ) . unwrap ( ) ;
1089
+
1090
+ // Remove the first page.
1091
+ mem. discard_range ( GuestAddress ( 0 ) , page_size) . unwrap ( ) ;
1092
+
1093
+ // Check that the first page is zeroed.
1094
+ let mut actual_page = vec ! [ 0u8 ; page_size] ;
1095
+ mem. read ( actual_page. as_mut_slice ( ) , GuestAddress ( 0 ) )
1096
+ . unwrap ( ) ;
1097
+ assert_eq ! ( vec![ 0u8 ; page_size] , actual_page) ;
1098
+ // Check that the second page still contains ones.
1099
+ mem. read ( actual_page. as_mut_slice ( ) , GuestAddress ( page_size as u64 ) )
1100
+ . unwrap ( ) ;
1101
+ assert_eq ! ( vec![ 1u8 ; page_size] , actual_page) ;
1102
+
1103
+ // Malformed range: the len is too big.
1104
+ assert_match ! (
1105
+ mem. discard_range( GuestAddress ( 0 ) , 0x10000 ) . unwrap_err( ) ,
1106
+ GuestMemoryError :: PartialBuffer {
1107
+ expected: _,
1108
+ completed: _
1109
+ }
1110
+ ) ;
1111
+
1112
+ // Region not mapped.
1113
+ assert_match ! (
1114
+ mem. discard_range( GuestAddress ( 0x10000 ) , 0x10 ) . unwrap_err( ) ,
1115
+ GuestMemoryError :: InvalidGuestAddress ( _)
1116
+ ) ;
1117
+ }
1047
1118
}
0 commit comments