virtio-devices: balloon: Implement free page reporting

Implement the VIRTIO_BALLOON_F_REPORTING feature, indicating to the
guest it can report set of free pages. A new virtqueue dedicated for
receiving the information about the free pages is created. The VMM
releases the memory by punching holes with fallocate() if the guest
memory is backed by a file, and madvise() the host about the ranges of
memory that shouldn't be needed anymore.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
This commit is contained in:
Sebastien Boeuf 2022-02-10 14:52:35 +01:00
parent 052f38fa96
commit 384752647a

View File

@ -41,8 +41,8 @@ use vm_migration::{
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
const QUEUE_SIZE: u16 = 128; const QUEUE_SIZE: u16 = 128;
const NUM_QUEUES: usize = 2; const REPORTING_QUEUE_SIZE: u16 = 32;
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE; NUM_QUEUES]; const MIN_NUM_QUEUES: usize = 2;
// Resize event. // Resize event.
const RESIZE_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 1; const RESIZE_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 1;
@ -50,12 +50,17 @@ const RESIZE_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 1;
const INFLATE_QUEUE_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 2; const INFLATE_QUEUE_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 2;
// Deflate virtio queue event. // Deflate virtio queue event.
const DEFLATE_QUEUE_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 3; const DEFLATE_QUEUE_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 3;
// Reporting virtio queue event.
const REPORTING_QUEUE_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 4;
// Size of a PFN in the balloon interface. // Size of a PFN in the balloon interface.
const VIRTIO_BALLOON_PFN_SHIFT: u64 = 12; const VIRTIO_BALLOON_PFN_SHIFT: u64 = 12;
// Deflate balloon on OOM // Deflate balloon on OOM
const VIRTIO_BALLOON_F_DEFLATE_ON_OOM: u64 = 2; const VIRTIO_BALLOON_F_DEFLATE_ON_OOM: u64 = 2;
// Enable an additional virtqueue to let the guest notify the host about free
// pages.
const VIRTIO_BALLOON_F_REPORTING: u64 = 5;
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
@ -162,6 +167,7 @@ struct BalloonEpollHandler {
interrupt_cb: Arc<dyn VirtioInterrupt>, interrupt_cb: Arc<dyn VirtioInterrupt>,
inflate_queue_evt: EventFd, inflate_queue_evt: EventFd,
deflate_queue_evt: EventFd, deflate_queue_evt: EventFd,
reporting_queue_evt: Option<EventFd>,
kill_evt: EventFd, kill_evt: EventFd,
pause_evt: EventFd, pause_evt: EventFd,
} }
@ -291,6 +297,25 @@ impl BalloonEpollHandler {
self.notify_queue(queue_index, used_descs) self.notify_queue(queue_index, used_descs)
} }
fn process_reporting_queue(&mut self, queue_index: usize) -> result::Result<(), Error> {
let mut used_descs = Vec::new();
for mut desc_chain in self.queues[queue_index]
.iter()
.map_err(Error::QueueIterator)?
{
let mut descs_len = 0;
while let Some(desc) = desc_chain.next() {
descs_len += desc.len();
Self::release_memory_range(desc_chain.memory(), desc.addr(), desc.len() as usize)?;
}
used_descs.push((desc_chain.head_index(), descs_len));
}
self.notify_queue(queue_index, used_descs)
}
fn run( fn run(
&mut self, &mut self,
paused: Arc<AtomicBool>, paused: Arc<AtomicBool>,
@ -300,6 +325,9 @@ impl BalloonEpollHandler {
helper.add_event(self.resize_receiver.evt.as_raw_fd(), RESIZE_EVENT)?; helper.add_event(self.resize_receiver.evt.as_raw_fd(), RESIZE_EVENT)?;
helper.add_event(self.inflate_queue_evt.as_raw_fd(), INFLATE_QUEUE_EVENT)?; helper.add_event(self.inflate_queue_evt.as_raw_fd(), INFLATE_QUEUE_EVENT)?;
helper.add_event(self.deflate_queue_evt.as_raw_fd(), DEFLATE_QUEUE_EVENT)?; helper.add_event(self.deflate_queue_evt.as_raw_fd(), DEFLATE_QUEUE_EVENT)?;
if let Some(reporting_queue_evt) = self.reporting_queue_evt.as_ref() {
helper.add_event(reporting_queue_evt.as_raw_fd(), REPORTING_QUEUE_EVENT)?;
}
helper.run(paused, paused_sync, self)?; helper.run(paused, paused_sync, self)?;
Ok(()) Ok(())
@ -357,6 +385,20 @@ impl EpollHelperHandler for BalloonEpollHandler {
return true; return true;
} }
} }
REPORTING_QUEUE_EVENT => {
if let Some(reporting_queue_evt) = self.reporting_queue_evt.as_ref() {
if let Err(e) = reporting_queue_evt.read() {
error!("Failed to get reporting queue event: {:?}", e);
return true;
} else if let Err(e) = self.process_reporting_queue(2) {
error!("Failed to signal used inflate queue: {:?}", e);
return true;
}
} else {
error!("Invalid reporting queue event as no eventfd registered");
return true;
}
}
_ => { _ => {
error!("Unknown event for virtio-balloon"); error!("Unknown event for virtio-balloon");
return true; return true;
@ -392,14 +434,19 @@ impl Balloon {
id: String, id: String,
size: u64, size: u64,
deflate_on_oom: bool, deflate_on_oom: bool,
_free_page_reporting: bool, free_page_reporting: bool,
seccomp_action: SeccompAction, seccomp_action: SeccompAction,
exit_evt: EventFd, exit_evt: EventFd,
) -> io::Result<Self> { ) -> io::Result<Self> {
let mut queue_sizes = vec![QUEUE_SIZE; MIN_NUM_QUEUES];
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1; let mut avail_features = 1u64 << VIRTIO_F_VERSION_1;
if deflate_on_oom { if deflate_on_oom {
avail_features |= 1u64 << VIRTIO_BALLOON_F_DEFLATE_ON_OOM; avail_features |= 1u64 << VIRTIO_BALLOON_F_DEFLATE_ON_OOM;
} }
if free_page_reporting {
avail_features |= 1u64 << VIRTIO_BALLOON_F_REPORTING;
queue_sizes.push(REPORTING_QUEUE_SIZE);
}
let config = VirtioBalloonConfig { let config = VirtioBalloonConfig {
num_pages: (size >> VIRTIO_BALLOON_PFN_SHIFT) as u32, num_pages: (size >> VIRTIO_BALLOON_PFN_SHIFT) as u32,
@ -411,8 +458,8 @@ impl Balloon {
device_type: VirtioDeviceType::Balloon as u32, device_type: VirtioDeviceType::Balloon as u32,
avail_features, avail_features,
paused_sync: Some(Arc::new(Barrier::new(2))), paused_sync: Some(Arc::new(Barrier::new(2))),
queue_sizes: QUEUE_SIZES.to_vec(), queue_sizes,
min_queues: NUM_QUEUES as u16, min_queues: MIN_NUM_QUEUES as u16,
..Default::default() ..Default::default()
}, },
id, id,
@ -503,6 +550,12 @@ impl VirtioDevice for Balloon {
let inflate_queue_evt = queue_evts.remove(0); let inflate_queue_evt = queue_evts.remove(0);
let deflate_queue_evt = queue_evts.remove(0); let deflate_queue_evt = queue_evts.remove(0);
let reporting_queue_evt =
if self.common.feature_acked(VIRTIO_BALLOON_F_REPORTING) && !queue_evts.is_empty() {
Some(queue_evts.remove(0))
} else {
None
};
let mut handler = BalloonEpollHandler { let mut handler = BalloonEpollHandler {
config: self.config.clone(), config: self.config.clone(),
@ -514,6 +567,7 @@ impl VirtioDevice for Balloon {
interrupt_cb, interrupt_cb,
inflate_queue_evt, inflate_queue_evt,
deflate_queue_evt, deflate_queue_evt,
reporting_queue_evt,
kill_evt, kill_evt,
pause_evt, pause_evt,
}; };