Skip to content

Commit c4d975a

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 c4d975a

File tree

11 files changed

+147
-29
lines changed

11 files changed

+147
-29
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: 92 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,16 @@ 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.device_state.active_state().ok_or(BalloonError::DeviceNotActive)?.mem;
268289
METRICS.inflate_count.inc();
269290

270291
let queue = &mut self.queues[INFLATE_INDEX];
@@ -406,6 +427,41 @@ impl Balloon {
406427
Ok(())
407428
}
408429

430+
pub(crate) fn process_free_page_reporting_queue(
431+
&mut self,
432+
) -> Result<(), BalloonError> {
433+
let mem = &self.device_state.active_state().unwrap().mem;
434+
435+
let idx = self.free_page_reporting_idx();
436+
let queue = &mut self.queues[idx];
437+
let mut needs_interrupt = false;
438+
439+
while let Some(head) = queue.pop()? {
440+
let head_index = head.index;
441+
442+
let mut last_desc = Some(head);
443+
while let Some(desc) = last_desc {
444+
if let Err(err) =
445+
mem.discard_range(desc.addr, desc.len as usize)
446+
{
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,11 @@ impl Balloon {
427483
return Err(err);
428484
}
429485

486+
if self.free_page_reporting() &&
487+
let Err(BalloonError::InvalidAvailIdx(err)) = self.process_free_page_reporting_queue() {
488+
return Err(err);
489+
}
490+
430491
Ok(())
431492
}
432493

@@ -466,6 +527,20 @@ impl Balloon {
466527
}
467528
}
468529

530+
pub fn free_page_reporting(&self) -> bool {
531+
self.avail_features & (1u64 << VIRTIO_BALLOON_F_FREE_PAGE_REPORTING) != 0
532+
}
533+
534+
pub fn free_page_reporting_idx(&self) -> usize {
535+
let mut idx = STATS_INDEX;
536+
537+
if self.stats_polling_interval_s > 0 {
538+
idx += 1;
539+
}
540+
541+
idx
542+
}
543+
469544
/// Update the statistics polling interval.
470545
pub fn update_stats_polling_interval(&mut self, interval_s: u16) -> Result<(), BalloonError> {
471546
if self.stats_polling_interval_s == interval_s {
@@ -529,6 +604,7 @@ impl Balloon {
529604
amount_mib: self.size_mb(),
530605
deflate_on_oom: self.deflate_on_oom(),
531606
stats_polling_interval_s: self.stats_polling_interval_s(),
607+
free_page_reporting: self.free_page_reporting(),
532608
}
533609
}
534610

@@ -737,7 +813,7 @@ pub(crate) mod tests {
737813
// Test all feature combinations.
738814
for deflate_on_oom in [true, false].iter() {
739815
for stats_interval in [0, 1].iter() {
740-
let mut balloon = Balloon::new(0, *deflate_on_oom, *stats_interval).unwrap();
816+
let mut balloon = Balloon::new(0, *deflate_on_oom, *stats_interval, false).unwrap();
741817
assert_eq!(balloon.device_type(), VIRTIO_ID_BALLOON);
742818

743819
let features: u64 = (1u64 << VIRTIO_F_VERSION_1)
@@ -764,12 +840,13 @@ pub(crate) mod tests {
764840

765841
#[test]
766842
fn test_virtio_read_config() {
767-
let balloon = Balloon::new(0x10, true, 0).unwrap();
843+
let balloon = Balloon::new(0x10, true, 0, false).unwrap();
768844

769845
let cfg = BalloonConfig {
770846
amount_mib: 16,
771847
deflate_on_oom: true,
772848
stats_polling_interval_s: 0,
849+
free_page_reporting: false,
773850
};
774851
assert_eq!(balloon.config(), cfg);
775852

@@ -798,7 +875,7 @@ pub(crate) mod tests {
798875

799876
#[test]
800877
fn test_virtio_write_config() {
801-
let mut balloon = Balloon::new(0, true, 0).unwrap();
878+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
802879

803880
let expected_config_space: [u8; BALLOON_CONFIG_SPACE_SIZE] =
804881
[0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
@@ -824,7 +901,7 @@ pub(crate) mod tests {
824901

825902
#[test]
826903
fn test_invalid_request() {
827-
let mut balloon = Balloon::new(0, true, 0).unwrap();
904+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
828905
let mem = default_mem();
829906
let interrupt = default_interrupt();
830907
// Only initialize the inflate queue to demonstrate invalid request handling.
@@ -885,7 +962,7 @@ pub(crate) mod tests {
885962

886963
#[test]
887964
fn test_inflate() {
888-
let mut balloon = Balloon::new(0, true, 0).unwrap();
965+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
889966
let mem = default_mem();
890967
let interrupt = default_interrupt();
891968
let infq = VirtQueue::new(GuestAddress(0), &mem, 16);
@@ -957,7 +1034,7 @@ pub(crate) mod tests {
9571034

9581035
#[test]
9591036
fn test_deflate() {
960-
let mut balloon = Balloon::new(0, true, 0).unwrap();
1037+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
9611038
let mem = default_mem();
9621039
let interrupt = default_interrupt();
9631040
let defq = VirtQueue::new(GuestAddress(0), &mem, 16);
@@ -1007,7 +1084,7 @@ pub(crate) mod tests {
10071084

10081085
#[test]
10091086
fn test_stats() {
1010-
let mut balloon = Balloon::new(0, true, 1).unwrap();
1087+
let mut balloon = Balloon::new(0, true, 1, false).unwrap();
10111088
let mem = default_mem();
10121089
let interrupt = default_interrupt();
10131090
let statsq = VirtQueue::new(GuestAddress(0), &mem, 16);
@@ -1099,7 +1176,7 @@ pub(crate) mod tests {
10991176

11001177
#[test]
11011178
fn test_process_balloon_queues() {
1102-
let mut balloon = Balloon::new(0x10, true, 0).unwrap();
1179+
let mut balloon = Balloon::new(0x10, true, 0, false).unwrap();
11031180
let mem = default_mem();
11041181
let interrupt = default_interrupt();
11051182
let infq = VirtQueue::new(GuestAddress(0), &mem, 16);
@@ -1114,7 +1191,7 @@ pub(crate) mod tests {
11141191

11151192
#[test]
11161193
fn test_update_stats_interval() {
1117-
let mut balloon = Balloon::new(0, true, 0).unwrap();
1194+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
11181195
let mem = default_mem();
11191196
let q = VirtQueue::new(GuestAddress(0), &mem, 16);
11201197
balloon.set_queue(INFLATE_INDEX, q.create_queue());
@@ -1127,7 +1204,7 @@ pub(crate) mod tests {
11271204
);
11281205
balloon.update_stats_polling_interval(0).unwrap();
11291206

1130-
let mut balloon = Balloon::new(0, true, 1).unwrap();
1207+
let mut balloon = Balloon::new(0, true, 1, false).unwrap();
11311208
let mem = default_mem();
11321209
let q = VirtQueue::new(GuestAddress(0), &mem, 16);
11331210
balloon.set_queue(INFLATE_INDEX, q.create_queue());
@@ -1145,14 +1222,14 @@ pub(crate) mod tests {
11451222

11461223
#[test]
11471224
fn test_cannot_update_inactive_device() {
1148-
let mut balloon = Balloon::new(0, true, 0).unwrap();
1225+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
11491226
// Assert that we can't update an inactive device.
11501227
balloon.update_size(1).unwrap_err();
11511228
}
11521229

11531230
#[test]
11541231
fn test_num_pages() {
1155-
let mut balloon = Balloon::new(0, true, 0).unwrap();
1232+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
11561233
// Switch the state to active.
11571234
balloon.device_state = DeviceState::Activated(ActiveState {
11581235
mem: single_region_mem(32 << 20),

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

Lines changed: 16 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,17 @@ impl Balloon {
4748
error!("Failed to register stats timerfd event: {}", err);
4849
}
4950
}
51+
52+
if self.free_page_reporting() && let Err(err) = ops.add(Events::with_data(
53+
&self.queue_evts[self.free_page_reporting_idx()],
54+
Self::PROCESS_VIRTQ_FREE_PAGE_REPORTING,
55+
EventSet::IN,
56+
)) {
57+
error!(
58+
"Failed to register free page reporting queue event: {}",
59+
err
60+
);
61+
}
5062
}
5163

5264
fn register_activate_event(&self, ops: &mut EventOps) {
@@ -103,6 +115,9 @@ impl MutEventSubscriber for Balloon {
103115
Self::PROCESS_STATS_TIMER => self
104116
.process_stats_timer_event()
105117
.unwrap_or_else(report_balloon_event_fail),
118+
Self::PROCESS_VIRTQ_FREE_PAGE_REPORTING => self
119+
.process_free_page_reporting_queue_event()
120+
.unwrap_or_else(report_balloon_event_fail),
106121
_ => {
107122
warn!("Balloon: Spurious event received: {:?}", source);
108123
}
@@ -142,7 +157,7 @@ pub mod tests {
142157
#[test]
143158
fn test_event_handler() {
144159
let mut event_manager = EventManager::new().unwrap();
145-
let mut balloon = Balloon::new(0, true, 10).unwrap();
160+
let mut balloon = Balloon::new(0, true, 10, false).unwrap();
146161
let mem = default_mem();
147162
let interrupt = default_interrupt();
148163
let infq = VirtQueue::new(GuestAddress(0), &mem, 16);

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

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,11 @@ pub const BALLOON_DEV_ID: &str = "balloon";
2525
/// The size of the config space.
2626
pub const BALLOON_CONFIG_SPACE_SIZE: usize = 8;
2727
/// Number of virtio queues.
28-
pub const BALLOON_NUM_QUEUES: usize = 3;
28+
pub const BALLOON_NUM_QUEUES: usize = 4;
2929
/// Virtio queue sizes, in number of descriptor chain heads.
30-
// There are 3 queues for a virtio device (in this order): RX, TX, Event
30+
// There are 4 queues for a virtio device (in this order): RX, TX, Event, Reporting
3131
pub const BALLOON_QUEUE_SIZES: [u16; BALLOON_NUM_QUEUES] = [
32-
FIRECRACKER_MAX_QUEUE_SIZE,
33-
FIRECRACKER_MAX_QUEUE_SIZE,
34-
FIRECRACKER_MAX_QUEUE_SIZE,
32+
FIRECRACKER_MAX_QUEUE_SIZE; BALLOON_NUM_QUEUES
3533
];
3634
// Number of 4K pages in a MiB.
3735
pub const MIB_TO_4K_PAGES: u32 = 256;
@@ -42,16 +40,17 @@ pub const MAX_PAGES_IN_DESC: usize = 256;
4240
pub const MAX_PAGE_COMPACT_BUFFER: usize = 2048;
4341
/// The addresses given by the driver are divided by 4096.
4442
pub const VIRTIO_BALLOON_PFN_SHIFT: u32 = 12;
45-
/// The index of the deflate queue from Balloon device queues/queues_evts vector.
43+
/// The index of the inflate queue from Balloon device queues/queues_evts vector.
4644
pub const INFLATE_INDEX: usize = 0;
4745
/// The index of the deflate queue from Balloon device queues/queues_evts vector.
4846
pub const DEFLATE_INDEX: usize = 1;
49-
/// The index of the deflate queue from Balloon device queues/queues_evts vector.
47+
/// The index of the stats queue from Balloon device queues/queues_evts vector.
5048
pub const STATS_INDEX: usize = 2;
5149

5250
// The feature bitmap for virtio balloon.
5351
const VIRTIO_BALLOON_F_STATS_VQ: u32 = 1; // Enable statistics.
5452
const VIRTIO_BALLOON_F_DEFLATE_ON_OOM: u32 = 2; // Deflate balloon on OOM.
53+
const VIRTIO_BALLOON_F_FREE_PAGE_REPORTING: u32 = 5; // Enable free page reportin
5554

5655
// The statistics tags.
5756
const VIRTIO_BALLOON_S_SWAP_IN: u16 = 0;

0 commit comments

Comments
 (0)