Skip to content

Commit 5c9e4c7

Browse files
committed
virtio-balloon: Add free page reporting
Free page reporting is a mechanism in which the guest will notify the host of pages which are not currently in use. This feature can only be configured on boot and will continue to report continuously. With free page reporting firecracker will `MADV_DONTNEED` on the ranges reported. This allows the host to free up memory and reduce the RSS of the VM. With UFFD this is sent as the `UFFD_EVENT_REMOVE` after the call with `MADV_DONTNEED`. Signed-off-by: Jack Thomson <jackabt@amazon.com>
1 parent 30c04f0 commit 5c9e4c7

File tree

11 files changed

+157
-31
lines changed

11 files changed

+157
-31
lines changed

src/vmm/src/builder.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1231,6 +1231,7 @@ pub(crate) mod tests {
12311231
amount_mib: 0,
12321232
deflate_on_oom: false,
12331233
stats_polling_interval_s: 0,
1234+
free_page_reporting: false,
12341235
};
12351236

12361237
let mut cmdline = default_kernel_cmdline();

src/vmm/src/device_manager/pci_mngr.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,7 @@ mod tests {
645645
amount_mib: 123,
646646
deflate_on_oom: false,
647647
stats_polling_interval_s: 1,
648+
free_page_reporting: false,
648649
};
649650
insert_balloon_device(&mut vmm, &mut cmdline, &mut event_manager, balloon_cfg);
650651
// Add a block device.
@@ -728,7 +729,8 @@ mod tests {
728729
"balloon": {{
729730
"amount_mib": 123,
730731
"deflate_on_oom": false,
731-
"stats_polling_interval_s": 1
732+
"stats_polling_interval_s": 1,
733+
"free_page_reporting": false
732734
}},
733735
"drives": [
734736
{{

src/vmm/src/device_manager/persist.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,7 @@ mod tests {
669669
amount_mib: 123,
670670
deflate_on_oom: false,
671671
stats_polling_interval_s: 1,
672+
free_page_reporting: false,
672673
};
673674
insert_balloon_device(&mut vmm, &mut cmdline, &mut event_manager, balloon_cfg);
674675
// Add a block device.
@@ -748,7 +749,8 @@ mod tests {
748749
"balloon": {{
749750
"amount_mib": 123,
750751
"deflate_on_oom": false,
751-
"stats_polling_interval_s": 1
752+
"stats_polling_interval_s": 1,
753+
"free_page_reporting": false
752754
}},
753755
"drives": [
754756
{{

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

Lines changed: 94 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use super::{
2424
VIRTIO_BALLOON_S_MEMTOT, VIRTIO_BALLOON_S_MINFLT, VIRTIO_BALLOON_S_SWAP_IN,
2525
VIRTIO_BALLOON_S_SWAP_OUT,
2626
};
27-
use crate::devices::virtio::balloon::BalloonError;
27+
use crate::devices::virtio::balloon::{BalloonError, VIRTIO_BALLOON_F_FREE_PAGE_REPORTING};
2828
use crate::devices::virtio::device::ActiveState;
2929
use crate::devices::virtio::generated::virtio_config::VIRTIO_F_VERSION_1;
3030
use crate::devices::virtio::generated::virtio_ids::VIRTIO_ID_BALLOON;
@@ -83,6 +83,9 @@ pub struct BalloonConfig {
8383
pub deflate_on_oom: bool,
8484
/// Interval of time in seconds at which the balloon statistics are updated.
8585
pub stats_polling_interval_s: u16,
86+
/// Free page reporting enabled
87+
#[serde(default)]
88+
pub free_page_reporting: bool,
8689
}
8790

8891
/// BalloonStats holds statistics returned from the stats_queue.
@@ -189,6 +192,7 @@ impl Balloon {
189192
amount_mib: u32,
190193
deflate_on_oom: bool,
191194
stats_polling_interval_s: u16,
195+
free_page_reporting: bool,
192196
) -> Result<Balloon, BalloonError> {
193197
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1;
194198

@@ -204,16 +208,26 @@ impl Balloon {
204208
EventFd::new(libc::EFD_NONBLOCK).map_err(BalloonError::EventFd)?,
205209
EventFd::new(libc::EFD_NONBLOCK).map_err(BalloonError::EventFd)?,
206210
EventFd::new(libc::EFD_NONBLOCK).map_err(BalloonError::EventFd)?,
211+
EventFd::new(libc::EFD_NONBLOCK).map_err(BalloonError::EventFd)?,
207212
];
208213

209214
let mut queues: Vec<Queue> = BALLOON_QUEUE_SIZES.iter().map(|&s| Queue::new(s)).collect();
210215

211216
// The VirtIO specification states that the statistics queue should
212217
// not be present at all if the statistics are not enabled.
218+
let mut dropped_queue_count = 0;
213219
if stats_polling_interval_s == 0 {
214-
let _ = queues.remove(STATS_INDEX);
220+
dropped_queue_count += 1;
221+
}
222+
223+
if free_page_reporting {
224+
avail_features |= 1u64 << VIRTIO_BALLOON_F_FREE_PAGE_REPORTING;
225+
} else {
226+
dropped_queue_count += 1;
215227
}
216228

229+
queues.truncate(queues.len() - dropped_queue_count);
230+
217231
let stats_timer =
218232
TimerFd::new_custom(ClockId::Monotonic, true, true).map_err(BalloonError::Timer)?;
219233

@@ -262,9 +276,20 @@ impl Balloon {
262276
self.trigger_stats_update()
263277
}
264278

279+
pub(crate) fn process_free_page_reporting_queue_event(&mut self) -> Result<(), BalloonError> {
280+
self.queue_evts[self.free_page_reporting_idx()]
281+
.read()
282+
.map_err(BalloonError::EventFd)?;
283+
self.process_free_page_reporting_queue()
284+
}
285+
265286
pub(crate) fn process_inflate(&mut self) -> Result<(), BalloonError> {
266287
// This is safe since we checked in the event handler that the device is activated.
267-
let mem = &self.device_state.active_state().unwrap().mem;
288+
let mem = &self
289+
.device_state
290+
.active_state()
291+
.ok_or(BalloonError::DeviceNotActive)?
292+
.mem;
268293
METRICS.inflate_count.inc();
269294

270295
let queue = &mut self.queues[INFLATE_INDEX];
@@ -406,6 +431,37 @@ impl Balloon {
406431
Ok(())
407432
}
408433

434+
pub(crate) fn process_free_page_reporting_queue(&mut self) -> Result<(), BalloonError> {
435+
let mem = &self.device_state.active_state().unwrap().mem;
436+
437+
let idx = self.free_page_reporting_idx();
438+
let queue = &mut self.queues[idx];
439+
let mut needs_interrupt = false;
440+
441+
while let Some(head) = queue.pop()? {
442+
let head_index = head.index;
443+
444+
let mut last_desc = Some(head);
445+
while let Some(desc) = last_desc {
446+
if let Err(err) = mem.discard_range(desc.addr, desc.len as usize) {
447+
error!("balloon: failed to remove range: {err:?}");
448+
}
449+
last_desc = desc.next_descriptor();
450+
}
451+
452+
queue.add_used(head.index, 0)?;
453+
needs_interrupt = true;
454+
}
455+
456+
queue.advance_used_ring_idx();
457+
458+
if needs_interrupt {
459+
self.signal_used_queue(idx)?;
460+
}
461+
462+
Ok(())
463+
}
464+
409465
pub(crate) fn signal_used_queue(&self, qidx: usize) -> Result<(), BalloonError> {
410466
self.interrupt_trigger()
411467
.trigger(VirtioInterruptType::Queue(
@@ -427,6 +483,13 @@ impl Balloon {
427483
return Err(err);
428484
}
429485

486+
if self.free_page_reporting()
487+
&& let Err(BalloonError::InvalidAvailIdx(err)) =
488+
self.process_free_page_reporting_queue()
489+
{
490+
return Err(err);
491+
}
492+
430493
Ok(())
431494
}
432495

@@ -466,6 +529,20 @@ impl Balloon {
466529
}
467530
}
468531

532+
pub fn free_page_reporting(&self) -> bool {
533+
self.avail_features & (1u64 << VIRTIO_BALLOON_F_FREE_PAGE_REPORTING) != 0
534+
}
535+
536+
pub fn free_page_reporting_idx(&self) -> usize {
537+
let mut idx = STATS_INDEX;
538+
539+
if self.stats_polling_interval_s > 0 {
540+
idx += 1;
541+
}
542+
543+
idx
544+
}
545+
469546
/// Update the statistics polling interval.
470547
pub fn update_stats_polling_interval(&mut self, interval_s: u16) -> Result<(), BalloonError> {
471548
if self.stats_polling_interval_s == interval_s {
@@ -529,6 +606,7 @@ impl Balloon {
529606
amount_mib: self.size_mb(),
530607
deflate_on_oom: self.deflate_on_oom(),
531608
stats_polling_interval_s: self.stats_polling_interval_s(),
609+
free_page_reporting: self.free_page_reporting(),
532610
}
533611
}
534612

@@ -737,7 +815,7 @@ pub(crate) mod tests {
737815
// Test all feature combinations.
738816
for deflate_on_oom in [true, false].iter() {
739817
for stats_interval in [0, 1].iter() {
740-
let mut balloon = Balloon::new(0, *deflate_on_oom, *stats_interval).unwrap();
818+
let mut balloon = Balloon::new(0, *deflate_on_oom, *stats_interval, false).unwrap();
741819
assert_eq!(balloon.device_type(), VIRTIO_ID_BALLOON);
742820

743821
let features: u64 = (1u64 << VIRTIO_F_VERSION_1)
@@ -764,12 +842,13 @@ pub(crate) mod tests {
764842

765843
#[test]
766844
fn test_virtio_read_config() {
767-
let balloon = Balloon::new(0x10, true, 0).unwrap();
845+
let balloon = Balloon::new(0x10, true, 0, false).unwrap();
768846

769847
let cfg = BalloonConfig {
770848
amount_mib: 16,
771849
deflate_on_oom: true,
772850
stats_polling_interval_s: 0,
851+
free_page_reporting: false,
773852
};
774853
assert_eq!(balloon.config(), cfg);
775854

@@ -798,7 +877,7 @@ pub(crate) mod tests {
798877

799878
#[test]
800879
fn test_virtio_write_config() {
801-
let mut balloon = Balloon::new(0, true, 0).unwrap();
880+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
802881

803882
let expected_config_space: [u8; BALLOON_CONFIG_SPACE_SIZE] =
804883
[0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
@@ -824,7 +903,7 @@ pub(crate) mod tests {
824903

825904
#[test]
826905
fn test_invalid_request() {
827-
let mut balloon = Balloon::new(0, true, 0).unwrap();
906+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
828907
let mem = default_mem();
829908
let interrupt = default_interrupt();
830909
// Only initialize the inflate queue to demonstrate invalid request handling.
@@ -885,7 +964,7 @@ pub(crate) mod tests {
885964

886965
#[test]
887966
fn test_inflate() {
888-
let mut balloon = Balloon::new(0, true, 0).unwrap();
967+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
889968
let mem = default_mem();
890969
let interrupt = default_interrupt();
891970
let infq = VirtQueue::new(GuestAddress(0), &mem, 16);
@@ -957,7 +1036,7 @@ pub(crate) mod tests {
9571036

9581037
#[test]
9591038
fn test_deflate() {
960-
let mut balloon = Balloon::new(0, true, 0).unwrap();
1039+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
9611040
let mem = default_mem();
9621041
let interrupt = default_interrupt();
9631042
let defq = VirtQueue::new(GuestAddress(0), &mem, 16);
@@ -1007,7 +1086,7 @@ pub(crate) mod tests {
10071086

10081087
#[test]
10091088
fn test_stats() {
1010-
let mut balloon = Balloon::new(0, true, 1).unwrap();
1089+
let mut balloon = Balloon::new(0, true, 1, false).unwrap();
10111090
let mem = default_mem();
10121091
let interrupt = default_interrupt();
10131092
let statsq = VirtQueue::new(GuestAddress(0), &mem, 16);
@@ -1099,7 +1178,7 @@ pub(crate) mod tests {
10991178

11001179
#[test]
11011180
fn test_process_balloon_queues() {
1102-
let mut balloon = Balloon::new(0x10, true, 0).unwrap();
1181+
let mut balloon = Balloon::new(0x10, true, 0, false).unwrap();
11031182
let mem = default_mem();
11041183
let interrupt = default_interrupt();
11051184
let infq = VirtQueue::new(GuestAddress(0), &mem, 16);
@@ -1114,7 +1193,7 @@ pub(crate) mod tests {
11141193

11151194
#[test]
11161195
fn test_update_stats_interval() {
1117-
let mut balloon = Balloon::new(0, true, 0).unwrap();
1196+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
11181197
let mem = default_mem();
11191198
let q = VirtQueue::new(GuestAddress(0), &mem, 16);
11201199
balloon.set_queue(INFLATE_INDEX, q.create_queue());
@@ -1127,7 +1206,7 @@ pub(crate) mod tests {
11271206
);
11281207
balloon.update_stats_polling_interval(0).unwrap();
11291208

1130-
let mut balloon = Balloon::new(0, true, 1).unwrap();
1209+
let mut balloon = Balloon::new(0, true, 1, false).unwrap();
11311210
let mem = default_mem();
11321211
let q = VirtQueue::new(GuestAddress(0), &mem, 16);
11331212
balloon.set_queue(INFLATE_INDEX, q.create_queue());
@@ -1145,14 +1224,14 @@ pub(crate) mod tests {
11451224

11461225
#[test]
11471226
fn test_cannot_update_inactive_device() {
1148-
let mut balloon = Balloon::new(0, true, 0).unwrap();
1227+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
11491228
// Assert that we can't update an inactive device.
11501229
balloon.update_size(1).unwrap_err();
11511230
}
11521231

11531232
#[test]
11541233
fn test_num_pages() {
1155-
let mut balloon = Balloon::new(0, true, 0).unwrap();
1234+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
11561235
// Switch the state to active.
11571236
balloon.device_state = DeviceState::Activated(ActiveState {
11581237
mem: single_region_mem(32 << 20),

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ impl Balloon {
1515
const PROCESS_VIRTQ_DEFLATE: u32 = 2;
1616
const PROCESS_VIRTQ_STATS: u32 = 3;
1717
const PROCESS_STATS_TIMER: u32 = 4;
18+
const PROCESS_VIRTQ_FREE_PAGE_REPORTING: u32 = 6;
1819

1920
fn register_runtime_events(&self, ops: &mut EventOps) {
2021
if let Err(err) = ops.add(Events::with_data(
@@ -47,6 +48,19 @@ impl Balloon {
4748
error!("Failed to register stats timerfd event: {}", err);
4849
}
4950
}
51+
52+
if self.free_page_reporting()
53+
&& let Err(err) = ops.add(Events::with_data(
54+
&self.queue_evts[self.free_page_reporting_idx()],
55+
Self::PROCESS_VIRTQ_FREE_PAGE_REPORTING,
56+
EventSet::IN,
57+
))
58+
{
59+
error!(
60+
"Failed to register free page reporting queue event: {}",
61+
err
62+
);
63+
}
5064
}
5165

5266
fn register_activate_event(&self, ops: &mut EventOps) {
@@ -103,6 +117,9 @@ impl MutEventSubscriber for Balloon {
103117
Self::PROCESS_STATS_TIMER => self
104118
.process_stats_timer_event()
105119
.unwrap_or_else(report_balloon_event_fail),
120+
Self::PROCESS_VIRTQ_FREE_PAGE_REPORTING => self
121+
.process_free_page_reporting_queue_event()
122+
.unwrap_or_else(report_balloon_event_fail),
106123
_ => {
107124
warn!("Balloon: Spurious event received: {:?}", source);
108125
}
@@ -142,7 +159,7 @@ pub mod tests {
142159
#[test]
143160
fn test_event_handler() {
144161
let mut event_manager = EventManager::new().unwrap();
145-
let mut balloon = Balloon::new(0, true, 10).unwrap();
162+
let mut balloon = Balloon::new(0, true, 10, false).unwrap();
146163
let mem = default_mem();
147164
let interrupt = default_interrupt();
148165
let infq = VirtQueue::new(GuestAddress(0), &mem, 16);

0 commit comments

Comments
 (0)