Skip to content

Commit aad789c

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 <[email protected]>
1 parent fa2627a commit aad789c

File tree

11 files changed

+168
-47
lines changed

11 files changed

+168
-47
lines changed

src/vmm/src/builder.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,6 +1233,7 @@ pub(crate) mod tests {
12331233
amount_mib: 0,
12341234
deflate_on_oom: false,
12351235
stats_polling_interval_s: 0,
1236+
free_page_reporting: false,
12361237
};
12371238

12381239
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
@@ -649,6 +649,7 @@ mod tests {
649649
amount_mib: 123,
650650
deflate_on_oom: false,
651651
stats_polling_interval_s: 1,
652+
free_page_reporting: false,
652653
};
653654
insert_balloon_device(&mut vmm, &mut cmdline, &mut event_manager, balloon_cfg);
654655
// 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/devices/virtio/balloon/device.rs

Lines changed: 105 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ use super::super::queue::Queue;
1616
use super::metrics::METRICS;
1717
use super::util::compact_page_frame_numbers;
1818
use super::{
19-
BALLOON_DEV_ID, BALLOON_NUM_QUEUES, BALLOON_QUEUE_SIZES, DEFLATE_INDEX, INFLATE_INDEX,
20-
MAX_PAGE_COMPACT_BUFFER, MAX_PAGES_IN_DESC, MIB_TO_4K_PAGES, STATS_INDEX,
21-
VIRTIO_BALLOON_F_DEFLATE_ON_OOM, VIRTIO_BALLOON_F_STATS_VQ, VIRTIO_BALLOON_PFN_SHIFT,
22-
VIRTIO_BALLOON_S_AVAIL, VIRTIO_BALLOON_S_CACHES, VIRTIO_BALLOON_S_HTLB_PGALLOC,
23-
VIRTIO_BALLOON_S_HTLB_PGFAIL, VIRTIO_BALLOON_S_MAJFLT, VIRTIO_BALLOON_S_MEMFREE,
24-
VIRTIO_BALLOON_S_MEMTOT, VIRTIO_BALLOON_S_MINFLT, VIRTIO_BALLOON_S_SWAP_IN,
25-
VIRTIO_BALLOON_S_SWAP_OUT,
19+
BALLOON_DEV_ID, BALLOON_MIN_NUM_QUEUES, BALLOON_NUM_QUEUES, BALLOON_QUEUE_SIZE, DEFLATE_INDEX,
20+
INFLATE_INDEX, MAX_PAGE_COMPACT_BUFFER, MAX_PAGES_IN_DESC, MIB_TO_4K_PAGES, STATS_INDEX,
21+
VIRTIO_BALLOON_F_DEFLATE_ON_OOM, VIRTIO_BALLOON_F_FREE_PAGE_REPORTING,
22+
VIRTIO_BALLOON_F_STATS_VQ, VIRTIO_BALLOON_PFN_SHIFT, VIRTIO_BALLOON_S_AVAIL,
23+
VIRTIO_BALLOON_S_CACHES, VIRTIO_BALLOON_S_HTLB_PGALLOC, VIRTIO_BALLOON_S_HTLB_PGFAIL,
24+
VIRTIO_BALLOON_S_MAJFLT, VIRTIO_BALLOON_S_MEMFREE, VIRTIO_BALLOON_S_MEMTOT,
25+
VIRTIO_BALLOON_S_MINFLT, VIRTIO_BALLOON_S_SWAP_IN, VIRTIO_BALLOON_S_SWAP_OUT,
2626
};
2727
use crate::devices::virtio::balloon::BalloonError;
2828
use crate::devices::virtio::device::ActiveState;
@@ -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,25 @@ 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.
213-
if stats_polling_interval_s == 0 {
214-
let _ = queues.remove(STATS_INDEX);
209+
let mut queue_count = BALLOON_MIN_NUM_QUEUES;
210+
if stats_polling_interval_s > 0 {
211+
queue_count += 1;
215212
}
216213

214+
if free_page_reporting {
215+
avail_features |= 1u64 << VIRTIO_BALLOON_F_FREE_PAGE_REPORTING;
216+
queue_count += 1;
217+
}
218+
219+
let queues: Vec<Queue> = (0..queue_count)
220+
.map(|_| Queue::new(BALLOON_QUEUE_SIZE))
221+
.collect();
222+
let queue_evts = (0..queue_count)
223+
.map(|_| EventFd::new(libc::EFD_NONBLOCK).map_err(BalloonError::EventFd))
224+
.collect::<Result<Vec<_>, _>>()?;
225+
217226
let stats_timer =
218227
TimerFd::new_custom(ClockId::Monotonic, true, true).map_err(BalloonError::Timer)?;
219228

@@ -262,9 +271,20 @@ impl Balloon {
262271
self.trigger_stats_update()
263272
}
264273

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

270290
let queue = &mut self.queues[INFLATE_INDEX];
@@ -406,6 +426,37 @@ impl Balloon {
406426
Ok(())
407427
}
408428

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

481+
if self.free_page_reporting()
482+
&& let Err(BalloonError::InvalidAvailIdx(err)) =
483+
self.process_free_page_reporting_queue()
484+
{
485+
return Err(err);
486+
}
487+
430488
Ok(())
431489
}
432490

@@ -466,6 +524,20 @@ impl Balloon {
466524
}
467525
}
468526

527+
pub fn free_page_reporting(&self) -> bool {
528+
self.avail_features & (1u64 << VIRTIO_BALLOON_F_FREE_PAGE_REPORTING) != 0
529+
}
530+
531+
pub fn free_page_reporting_idx(&self) -> usize {
532+
let mut idx = STATS_INDEX;
533+
534+
if self.stats_polling_interval_s > 0 {
535+
idx += 1;
536+
}
537+
538+
idx
539+
}
540+
469541
/// Update the statistics polling interval.
470542
pub fn update_stats_polling_interval(&mut self, interval_s: u16) -> Result<(), BalloonError> {
471543
if self.stats_polling_interval_s == interval_s {
@@ -529,6 +601,7 @@ impl Balloon {
529601
amount_mib: self.size_mb(),
530602
deflate_on_oom: self.deflate_on_oom(),
531603
stats_polling_interval_s: self.stats_polling_interval_s(),
604+
free_page_reporting: self.free_page_reporting(),
532605
}
533606
}
534607

@@ -737,7 +810,7 @@ pub(crate) mod tests {
737810
// Test all feature combinations.
738811
for deflate_on_oom in [true, false].iter() {
739812
for stats_interval in [0, 1].iter() {
740-
let mut balloon = Balloon::new(0, *deflate_on_oom, *stats_interval).unwrap();
813+
let mut balloon = Balloon::new(0, *deflate_on_oom, *stats_interval, false).unwrap();
741814
assert_eq!(balloon.device_type(), VIRTIO_ID_BALLOON);
742815

743816
let features: u64 = (1u64 << VIRTIO_F_VERSION_1)
@@ -764,12 +837,13 @@ pub(crate) mod tests {
764837

765838
#[test]
766839
fn test_virtio_read_config() {
767-
let balloon = Balloon::new(0x10, true, 0).unwrap();
840+
let balloon = Balloon::new(0x10, true, 0, false).unwrap();
768841

769842
let cfg = BalloonConfig {
770843
amount_mib: 16,
771844
deflate_on_oom: true,
772845
stats_polling_interval_s: 0,
846+
free_page_reporting: false,
773847
};
774848
assert_eq!(balloon.config(), cfg);
775849

@@ -798,7 +872,7 @@ pub(crate) mod tests {
798872

799873
#[test]
800874
fn test_virtio_write_config() {
801-
let mut balloon = Balloon::new(0, true, 0).unwrap();
875+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
802876

803877
let expected_config_space: [u8; BALLOON_CONFIG_SPACE_SIZE] =
804878
[0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
@@ -824,7 +898,7 @@ pub(crate) mod tests {
824898

825899
#[test]
826900
fn test_invalid_request() {
827-
let mut balloon = Balloon::new(0, true, 0).unwrap();
901+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
828902
let mem = default_mem();
829903
let interrupt = default_interrupt();
830904
// Only initialize the inflate queue to demonstrate invalid request handling.
@@ -885,7 +959,7 @@ pub(crate) mod tests {
885959

886960
#[test]
887961
fn test_inflate() {
888-
let mut balloon = Balloon::new(0, true, 0).unwrap();
962+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
889963
let mem = default_mem();
890964
let interrupt = default_interrupt();
891965
let infq = VirtQueue::new(GuestAddress(0), &mem, 16);
@@ -957,7 +1031,7 @@ pub(crate) mod tests {
9571031

9581032
#[test]
9591033
fn test_deflate() {
960-
let mut balloon = Balloon::new(0, true, 0).unwrap();
1034+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
9611035
let mem = default_mem();
9621036
let interrupt = default_interrupt();
9631037
let defq = VirtQueue::new(GuestAddress(0), &mem, 16);
@@ -1007,7 +1081,7 @@ pub(crate) mod tests {
10071081

10081082
#[test]
10091083
fn test_stats() {
1010-
let mut balloon = Balloon::new(0, true, 1).unwrap();
1084+
let mut balloon = Balloon::new(0, true, 1, false).unwrap();
10111085
let mem = default_mem();
10121086
let interrupt = default_interrupt();
10131087
let statsq = VirtQueue::new(GuestAddress(0), &mem, 16);
@@ -1099,7 +1173,7 @@ pub(crate) mod tests {
10991173

11001174
#[test]
11011175
fn test_process_balloon_queues() {
1102-
let mut balloon = Balloon::new(0x10, true, 0).unwrap();
1176+
let mut balloon = Balloon::new(0x10, true, 0, false).unwrap();
11031177
let mem = default_mem();
11041178
let interrupt = default_interrupt();
11051179
let infq = VirtQueue::new(GuestAddress(0), &mem, 16);
@@ -1114,7 +1188,7 @@ pub(crate) mod tests {
11141188

11151189
#[test]
11161190
fn test_update_stats_interval() {
1117-
let mut balloon = Balloon::new(0, true, 0).unwrap();
1191+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
11181192
let mem = default_mem();
11191193
let q = VirtQueue::new(GuestAddress(0), &mem, 16);
11201194
balloon.set_queue(INFLATE_INDEX, q.create_queue());
@@ -1127,7 +1201,7 @@ pub(crate) mod tests {
11271201
);
11281202
balloon.update_stats_polling_interval(0).unwrap();
11291203

1130-
let mut balloon = Balloon::new(0, true, 1).unwrap();
1204+
let mut balloon = Balloon::new(0, true, 1, false).unwrap();
11311205
let mem = default_mem();
11321206
let q = VirtQueue::new(GuestAddress(0), &mem, 16);
11331207
balloon.set_queue(INFLATE_INDEX, q.create_queue());
@@ -1145,14 +1219,14 @@ pub(crate) mod tests {
11451219

11461220
#[test]
11471221
fn test_cannot_update_inactive_device() {
1148-
let mut balloon = Balloon::new(0, true, 0).unwrap();
1222+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
11491223
// Assert that we can't update an inactive device.
11501224
balloon.update_size(1).unwrap_err();
11511225
}
11521226

11531227
#[test]
11541228
fn test_num_pages() {
1155-
let mut balloon = Balloon::new(0, true, 0).unwrap();
1229+
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
11561230
// Switch the state to active.
11571231
balloon.device_state = DeviceState::Activated(ActiveState {
11581232
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)