Skip to content

Commit 9536eaa

Browse files
committed
fix: only use mmap trick when restoring from file
When the balloon inflates, and the guest gives us back some pages of memory, we need to free those pages. In booted VMs, we do this with madvise(MADV_DONTNEED), and in restored VMs we do it by MAP_FIXED-ing a new VMA on top of the range-to-free. This is because if guest memory is a MAP_PRIVATE of a memory file, madvise has no effect. However, we also do this MAP_FIXED trick if the snapshot is restored with UFFD. In this case, its not needed (madvise works perfectly fine), and in fact, its wrong: If we map over the memory range, UFFD will not receive Remove events for the specified range. Fix this by only using the mmap trick for file-based restored. Fixes #4988 Signed-off-by: Patrick Roy <[email protected]>
1 parent bb1edd1 commit 9536eaa

File tree

6 files changed

+26
-13
lines changed

6 files changed

+26
-13
lines changed

src/vmm/src/builder.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,7 @@ pub fn build_microvm_from_snapshot(
531531
resource_allocator: &mut vmm.resource_allocator,
532532
vm_resources,
533533
instance_id: &instance_info.id,
534+
restored_from_file: vmm.uffd.is_none(),
534535
};
535536

536537
vmm.mmio_device_manager =

src/vmm/src/device_manager/persist.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ pub struct MMIODevManagerConstructorArgs<'a> {
214214
pub resource_allocator: &'a mut ResourceAllocator,
215215
pub vm_resources: &'a mut VmResources,
216216
pub instance_id: &'a str,
217+
pub restored_from_file: bool,
217218
}
218219
impl fmt::Debug for MMIODevManagerConstructorArgs<'_> {
219220
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -512,7 +513,10 @@ impl<'a> Persist<'a> for MMIODeviceManager {
512513

513514
if let Some(balloon_state) = &state.balloon_device {
514515
let device = Arc::new(Mutex::new(Balloon::restore(
515-
BalloonConstructorArgs { mem: mem.clone() },
516+
BalloonConstructorArgs {
517+
mem: mem.clone(),
518+
restored_from_file: constructor_args.restored_from_file,
519+
},
516520
&balloon_state.device_state,
517521
)?));
518522

@@ -807,6 +811,7 @@ mod tests {
807811
resource_allocator: &mut resource_allocator,
808812
vm_resources,
809813
instance_id: "microvm-id",
814+
restored_from_file: true,
810815
};
811816
let restored_dev_manager =
812817
MMIODeviceManager::restore(restore_args, &device_states).unwrap();

src/vmm/src/devices/virtio/balloon/device.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ pub struct Balloon {
164164
pub(crate) irq_trigger: IrqTrigger,
165165

166166
// Implementation specific fields.
167-
pub(crate) restored: bool,
167+
pub(crate) restored_from_file: bool,
168168
pub(crate) stats_polling_interval_s: u16,
169169
pub(crate) stats_timer: TimerFd,
170170
// The index of the previous stats descriptor is saved because
@@ -189,7 +189,7 @@ impl fmt::Debug for Balloon {
189189
.field("queue_evts", &self.queue_evts)
190190
.field("device_state", &self.device_state)
191191
.field("irq_trigger", &self.irq_trigger)
192-
.field("restored", &self.restored)
192+
.field("restored_from_file", &self.restored_from_file)
193193
.field("stats_polling_interval_s", &self.stats_polling_interval_s)
194194
.field("stats_desc_index", &self.stats_desc_index)
195195
.field("latest_stats", &self.latest_stats)
@@ -204,7 +204,7 @@ impl Balloon {
204204
amount_mib: u32,
205205
deflate_on_oom: bool,
206206
stats_polling_interval_s: u16,
207-
restored: bool,
207+
restored_from_file: bool,
208208
) -> Result<Balloon, BalloonError> {
209209
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1;
210210

@@ -245,7 +245,7 @@ impl Balloon {
245245
irq_trigger: IrqTrigger::new().map_err(BalloonError::EventFd)?,
246246
device_state: DeviceState::Inactive,
247247
activate_evt: EventFd::new(libc::EFD_NONBLOCK).map_err(BalloonError::EventFd)?,
248-
restored,
248+
restored_from_file,
249249
stats_polling_interval_s,
250250
stats_timer,
251251
stats_desc_index: None,
@@ -355,7 +355,7 @@ impl Balloon {
355355
if let Err(err) = remove_range(
356356
mem,
357357
(guest_addr, u64::from(range_len) << VIRTIO_BALLOON_PFN_SHIFT),
358-
self.restored,
358+
self.restored_from_file,
359359
) {
360360
error!("Error removing memory range: {:?}", err);
361361
}

src/vmm/src/devices/virtio/balloon/persist.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ pub struct BalloonState {
9595
pub struct BalloonConstructorArgs {
9696
/// Pointer to guest memory.
9797
pub mem: GuestMemoryMmap,
98+
pub restored_from_file: bool,
9899
}
99100

100101
impl Persist<'_> for Balloon {
@@ -121,7 +122,12 @@ impl Persist<'_> for Balloon {
121122
) -> Result<Self, Self::Error> {
122123
// We can safely create the balloon with arbitrary flags and
123124
// num_pages because we will overwrite them after.
124-
let mut balloon = Balloon::new(0, false, state.stats_polling_interval_s, true)?;
125+
let mut balloon = Balloon::new(
126+
0,
127+
false,
128+
state.stats_polling_interval_s,
129+
constructor_args.restored_from_file,
130+
)?;
125131

126132
let mut num_queues = BALLOON_NUM_QUEUES;
127133
// As per the virtio 1.1 specification, the statistics queue
@@ -192,13 +198,16 @@ mod tests {
192198

193199
// Deserialize and restore the balloon device.
194200
let restored_balloon = Balloon::restore(
195-
BalloonConstructorArgs { mem: guest_mem },
201+
BalloonConstructorArgs {
202+
mem: guest_mem,
203+
restored_from_file: true,
204+
},
196205
&Snapshot::deserialize(&mut mem.as_slice()).unwrap(),
197206
)
198207
.unwrap();
199208

200209
assert_eq!(restored_balloon.device_type(), TYPE_BALLOON);
201-
assert!(restored_balloon.restored);
210+
assert!(restored_balloon.restored_from_file);
202211

203212
assert_eq!(restored_balloon.acked_features, balloon.acked_features);
204213
assert_eq!(restored_balloon.avail_features, balloon.avail_features);

src/vmm/src/devices/virtio/balloon/util.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ pub(crate) fn compact_page_frame_numbers(v: &mut [u32]) -> Vec<(u32, u32)> {
6868
pub(crate) fn remove_range(
6969
guest_memory: &GuestMemoryMmap,
7070
range: (GuestAddress, u64),
71-
restored: bool,
71+
restored_from_file: bool,
7272
) -> Result<(), RemoveRegionError> {
7373
let (guest_address, range_len) = range;
7474

@@ -83,7 +83,7 @@ pub(crate) fn remove_range(
8383
// Mmap a new anonymous region over the present one in order to create a hole.
8484
// This workaround is (only) needed after resuming from a snapshot because the guest memory
8585
// is mmaped from file as private and there is no `madvise` flag that works for this case.
86-
if restored {
86+
if restored_from_file {
8787
// SAFETY: The address and length are known to be valid.
8888
let ret = unsafe {
8989
libc::mmap(

src/vmm/src/lib.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,6 @@ pub struct Vmm {
314314
vm: Vm,
315315
guest_memory: GuestMemoryMmap,
316316
// Save UFFD in order to keep it open in the Firecracker process, as well.
317-
// Since this field is never read again, we need to allow `dead_code`.
318-
#[allow(dead_code)]
319317
uffd: Option<Uffd>,
320318
vcpus_handles: Vec<VcpuHandle>,
321319
// Used by Vcpus and devices to initiate teardown; Vmm should never write here.

0 commit comments

Comments
 (0)