Skip to content

Commit c130967

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 18da1d9 commit c130967

File tree

11 files changed

+161
-39
lines changed

11 files changed

+161
-39
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
@@ -661,6 +661,7 @@ mod tests {
661661
amount_mib: 123,
662662
deflate_on_oom: false,
663663
stats_polling_interval_s: 1,
664+
free_page_reporting: false,
664665
};
665666
insert_balloon_device(&mut vmm, &mut cmdline, &mut event_manager, balloon_cfg);
666667
// Add a block device.
@@ -740,7 +741,8 @@ mod tests {
740741
"balloon": {{
741742
"amount_mib": 123,
742743
"deflate_on_oom": false,
743-
"stats_polling_interval_s": 1
744+
"stats_polling_interval_s": 1,
745+
"free_page_reporting": false
744746
}},
745747
"drives": [
746748
{{

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

Lines changed: 99 additions & 24 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.
@@ -169,7 +172,7 @@ pub struct Balloon {
169172

170173
// Transport related fields.
171174
pub(crate) queues: Vec<Queue>,
172-
pub(crate) queue_evts: [EventFd; BALLOON_NUM_QUEUES],
175+
pub(crate) queue_evts: Vec<EventFd>,
173176
pub(crate) device_state: DeviceState,
174177

175178
// Implementation specific fields.
@@ -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

@@ -200,20 +204,26 @@ impl Balloon {
200204
avail_features |= 1u64 << VIRTIO_BALLOON_F_STATS_VQ;
201205
}
202206

203-
let queue_evts = [
204-
EventFd::new(libc::EFD_NONBLOCK).map_err(BalloonError::EventFd)?,
205-
EventFd::new(libc::EFD_NONBLOCK).map_err(BalloonError::EventFd)?,
206-
EventFd::new(libc::EFD_NONBLOCK).map_err(BalloonError::EventFd)?,
207-
];
208-
209-
let mut queues: Vec<Queue> = BALLOON_QUEUE_SIZES.iter().map(|&s| Queue::new(s)).collect();
210-
211207
// The VirtIO specification states that the statistics queue should
212208
// not be present at all if the statistics are not enabled.
209+
let mut queue_count = BALLOON_QUEUE_SIZES.len();
213210
if stats_polling_interval_s == 0 {
214-
let _ = queues.remove(STATS_INDEX);
211+
queue_count -= 1;
212+
}
213+
214+
if free_page_reporting {
215+
avail_features |= 1u64 << VIRTIO_BALLOON_F_FREE_PAGE_REPORTING;
216+
} else {
217+
queue_count -= 1;
215218
}
216219

220+
let queues: Vec<Queue> = (0..queue_count)
221+
.map(|s| Queue::new(BALLOON_QUEUE_SIZES[s]))
222+
.collect();
223+
let queue_evts = (0..queue_count)
224+
.map(|_| EventFd::new(libc::EFD_NONBLOCK).map_err(BalloonError::EventFd))
225+
.collect::<Result<Vec<_>, _>>()?;
226+
217227
let stats_timer =
218228
TimerFd::new_custom(ClockId::Monotonic, true, true).map_err(BalloonError::Timer)?;
219229

@@ -262,9 +272,20 @@ impl Balloon {
262272
self.trigger_stats_update()
263273
}
264274

275+
pub(crate) fn process_free_page_reporting_queue_event(&mut self) -> Result<(), BalloonError> {
276+
self.queue_evts[self.free_page_reporting_idx()]
277+
.read()
278+
.map_err(BalloonError::EventFd)?;
279+
self.process_free_page_reporting_queue()
280+
}
281+
265282
pub(crate) fn process_inflate(&mut self) -> Result<(), BalloonError> {
266283
// 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;
284+
let mem = &self
285+
.device_state
286+
.active_state()
287+
.ok_or(BalloonError::DeviceNotActive)?
288+
.mem;
268289
METRICS.inflate_count.inc();
269290

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

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

482+
if self.free_page_reporting()
483+
&& let Err(BalloonError::InvalidAvailIdx(err)) =
484+
self.process_free_page_reporting_queue()
485+
{
486+
return Err(err);
487+
}
488+
430489
Ok(())
431490
}
432491

@@ -466,6 +525,20 @@ impl Balloon {
466525
}
467526
}
468527

528+
pub fn free_page_reporting(&self) -> bool {
529+
self.avail_features & (1u64 << VIRTIO_BALLOON_F_FREE_PAGE_REPORTING) != 0
530+
}
531+
532+
pub fn free_page_reporting_idx(&self) -> usize {
533+
let mut idx = STATS_INDEX;
534+
535+
if self.stats_polling_interval_s > 0 {
536+
idx += 1;
537+
}
538+
539+
idx
540+
}
541+
469542
/// Update the statistics polling interval.
470543
pub fn update_stats_polling_interval(&mut self, interval_s: u16) -> Result<(), BalloonError> {
471544
if self.stats_polling_interval_s == interval_s {
@@ -529,6 +602,7 @@ impl Balloon {
529602
amount_mib: self.size_mb(),
530603
deflate_on_oom: self.deflate_on_oom(),
531604
stats_polling_interval_s: self.stats_polling_interval_s(),
605+
free_page_reporting: self.free_page_reporting(),
532606
}
533607
}
534608

@@ -737,7 +811,7 @@ pub(crate) mod tests {
737811
// Test all feature combinations.
738812
for deflate_on_oom in [true, false].iter() {
739813
for stats_interval in [0, 1].iter() {
740-
let mut balloon = Balloon::new(0, *deflate_on_oom, *stats_interval).unwrap();
814+
let mut balloon = Balloon::new(0, *deflate_on_oom, *stats_interval, false).unwrap();
741815
assert_eq!(balloon.device_type(), VIRTIO_ID_BALLOON);
742816

743817
let features: u64 = (1u64 << VIRTIO_F_VERSION_1)
@@ -764,12 +838,13 @@ pub(crate) mod tests {
764838

765839
#[test]
766840
fn test_virtio_read_config() {
767-
let balloon = Balloon::new(0x10, true, 0).unwrap();
841+
let balloon = Balloon::new(0x10, true, 0, false).unwrap();
768842

769843
let cfg = BalloonConfig {
770844
amount_mib: 16,
771845
deflate_on_oom: true,
772846
stats_polling_interval_s: 0,
847+
free_page_reporting: false,
773848
};
774849
assert_eq!(balloon.config(), cfg);
775850

@@ -798,7 +873,7 @@ pub(crate) mod tests {
798873

799874
#[test]
800875
fn test_virtio_write_config() {
801-
let mut balloon = Balloon::new(0, true, 0).unwrap();
876+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
802877

803878
let expected_config_space: [u8; BALLOON_CONFIG_SPACE_SIZE] =
804879
[0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
@@ -824,7 +899,7 @@ pub(crate) mod tests {
824899

825900
#[test]
826901
fn test_invalid_request() {
827-
let mut balloon = Balloon::new(0, true, 0).unwrap();
902+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
828903
let mem = default_mem();
829904
let interrupt = default_interrupt();
830905
// Only initialize the inflate queue to demonstrate invalid request handling.
@@ -885,7 +960,7 @@ pub(crate) mod tests {
885960

886961
#[test]
887962
fn test_inflate() {
888-
let mut balloon = Balloon::new(0, true, 0).unwrap();
963+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
889964
let mem = default_mem();
890965
let interrupt = default_interrupt();
891966
let infq = VirtQueue::new(GuestAddress(0), &mem, 16);
@@ -957,7 +1032,7 @@ pub(crate) mod tests {
9571032

9581033
#[test]
9591034
fn test_deflate() {
960-
let mut balloon = Balloon::new(0, true, 0).unwrap();
1035+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
9611036
let mem = default_mem();
9621037
let interrupt = default_interrupt();
9631038
let defq = VirtQueue::new(GuestAddress(0), &mem, 16);
@@ -1007,7 +1082,7 @@ pub(crate) mod tests {
10071082

10081083
#[test]
10091084
fn test_stats() {
1010-
let mut balloon = Balloon::new(0, true, 1).unwrap();
1085+
let mut balloon = Balloon::new(0, true, 1, false).unwrap();
10111086
let mem = default_mem();
10121087
let interrupt = default_interrupt();
10131088
let statsq = VirtQueue::new(GuestAddress(0), &mem, 16);
@@ -1099,7 +1174,7 @@ pub(crate) mod tests {
10991174

11001175
#[test]
11011176
fn test_process_balloon_queues() {
1102-
let mut balloon = Balloon::new(0x10, true, 0).unwrap();
1177+
let mut balloon = Balloon::new(0x10, true, 0, false).unwrap();
11031178
let mem = default_mem();
11041179
let interrupt = default_interrupt();
11051180
let infq = VirtQueue::new(GuestAddress(0), &mem, 16);
@@ -1114,7 +1189,7 @@ pub(crate) mod tests {
11141189

11151190
#[test]
11161191
fn test_update_stats_interval() {
1117-
let mut balloon = Balloon::new(0, true, 0).unwrap();
1192+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
11181193
let mem = default_mem();
11191194
let q = VirtQueue::new(GuestAddress(0), &mem, 16);
11201195
balloon.set_queue(INFLATE_INDEX, q.create_queue());
@@ -1127,7 +1202,7 @@ pub(crate) mod tests {
11271202
);
11281203
balloon.update_stats_polling_interval(0).unwrap();
11291204

1130-
let mut balloon = Balloon::new(0, true, 1).unwrap();
1205+
let mut balloon = Balloon::new(0, true, 1, false).unwrap();
11311206
let mem = default_mem();
11321207
let q = VirtQueue::new(GuestAddress(0), &mem, 16);
11331208
balloon.set_queue(INFLATE_INDEX, q.create_queue());
@@ -1145,14 +1220,14 @@ pub(crate) mod tests {
11451220

11461221
#[test]
11471222
fn test_cannot_update_inactive_device() {
1148-
let mut balloon = Balloon::new(0, true, 0).unwrap();
1223+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
11491224
// Assert that we can't update an inactive device.
11501225
balloon.update_size(1).unwrap_err();
11511226
}
11521227

11531228
#[test]
11541229
fn test_num_pages() {
1155-
let mut balloon = Balloon::new(0, true, 0).unwrap();
1230+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
11561231
// Switch the state to active.
11571232
balloon.device_state = DeviceState::Activated(ActiveState {
11581233
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)