virtio-devices: support event idx for virtio-blk

Support event idx feature for virtio-blk device.
This feature could improve disk IO performance by suppressing
notifications from guest to host and interrupts from host to
guest, which has been already supported in virtio-net and
vhost-user devices.

To achieve this, virtqueue's event-idx-related API is
leveraged for avail_event field update and needs_notification
check.

Fixes: #6580
Signed-off-by: wuxinyue <wuxinyue.wxy@antgroup.com>
This commit is contained in:
wuxinyue 2024-07-22 16:21:05 +08:00 committed by Rob Bradford
parent bb4af57219
commit a2438700e4

View File

@ -40,6 +40,7 @@ use std::sync::{Arc, Barrier};
use thiserror::Error; use thiserror::Error;
use virtio_bindings::virtio_blk::*; use virtio_bindings::virtio_blk::*;
use virtio_bindings::virtio_config::*; use virtio_bindings::virtio_config::*;
use virtio_bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
use virtio_queue::{Queue, QueueOwnedT, QueueT}; use virtio_queue::{Queue, QueueOwnedT, QueueT};
use vm_memory::{ByteValued, Bytes, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryError}; use vm_memory::{ByteValued, Bytes, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryError};
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
@ -79,6 +80,8 @@ pub enum Error {
QueueIterator(virtio_queue::Error), QueueIterator(virtio_queue::Error),
#[error("Failed to update request status: {0}")] #[error("Failed to update request status: {0}")]
RequestStatus(GuestMemoryError), RequestStatus(GuestMemoryError),
#[error("Failed to enable notification: {0}")]
QueueEnableNotification(virtio_queue::Error),
} }
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;
@ -137,11 +140,9 @@ struct BlockEpollHandler {
} }
impl BlockEpollHandler { impl BlockEpollHandler {
fn process_queue_submit(&mut self) -> Result<bool> { fn process_queue_submit(&mut self) -> Result<()> {
let queue = &mut self.queue; let queue = &mut self.queue;
let mut used_descs = false;
while let Some(mut desc_chain) = queue.pop_descriptor_chain(self.mem.memory()) { while let Some(mut desc_chain) = queue.pop_descriptor_chain(self.mem.memory()) {
let mut request = Request::parse(&mut desc_chain, self.access_platform.as_ref()) let mut request = Request::parse(&mut desc_chain, self.access_platform.as_ref())
.map_err(Error::RequestParsing)?; .map_err(Error::RequestParsing)?;
@ -163,7 +164,9 @@ impl BlockEpollHandler {
queue queue
.add_used(desc_chain.memory(), desc_chain.head_index(), 0) .add_used(desc_chain.memory(), desc_chain.head_index(), 0)
.map_err(Error::QueueAddUsed)?; .map_err(Error::QueueAddUsed)?;
used_descs = true; queue
.enable_notification(self.mem.memory().deref())
.map_err(Error::QueueEnableNotification)?;
continue; continue;
} }
@ -223,23 +226,34 @@ impl BlockEpollHandler {
queue queue
.add_used(desc_chain.memory(), desc_chain.head_index(), 0) .add_used(desc_chain.memory(), desc_chain.head_index(), 0)
.map_err(Error::QueueAddUsed)?; .map_err(Error::QueueAddUsed)?;
used_descs = true; queue
.enable_notification(self.mem.memory().deref())
.map_err(Error::QueueEnableNotification)?;
} }
} }
Ok(used_descs) Ok(())
} }
fn process_queue_submit_and_signal(&mut self) -> result::Result<(), EpollHelperError> { fn process_queue_submit_and_signal(&mut self) -> result::Result<(), EpollHelperError> {
let needs_notification = self.process_queue_submit().map_err(|e| { self.process_queue_submit().map_err(|e| {
EpollHelperError::HandleEvent(anyhow!("Failed to process queue (submit): {:?}", e)) EpollHelperError::HandleEvent(anyhow!("Failed to process queue (submit): {:?}", e))
})?; })?;
if needs_notification { if self
.queue
.needs_notification(self.mem.memory().deref())
.map_err(|e| {
EpollHelperError::HandleEvent(anyhow!(
"Failed to check needs_notification: {:?}",
e
))
})?
{
self.signal_used_queue().map_err(|e| { self.signal_used_queue().map_err(|e| {
EpollHelperError::HandleEvent(anyhow!("Failed to signal used queue: {:?}", e)) EpollHelperError::HandleEvent(anyhow!("Failed to signal used queue: {:?}", e))
})? })?;
}; }
Ok(()) Ok(())
} }
@ -265,8 +279,7 @@ impl BlockEpollHandler {
Err(Error::MissingEntryRequestList) Err(Error::MissingEntryRequestList)
} }
fn process_queue_complete(&mut self) -> Result<bool> { fn process_queue_complete(&mut self) -> Result<()> {
let mut used_descs = false;
let mem = self.mem.memory(); let mem = self.mem.memory();
let mut read_bytes = Wrapping(0); let mut read_bytes = Wrapping(0);
let mut write_bytes = Wrapping(0); let mut write_bytes = Wrapping(0);
@ -379,7 +392,9 @@ impl BlockEpollHandler {
queue queue
.add_used(mem.deref(), desc_index, len) .add_used(mem.deref(), desc_index, len)
.map_err(Error::QueueAddUsed)?; .map_err(Error::QueueAddUsed)?;
used_descs = true; queue
.enable_notification(mem.deref())
.map_err(Error::QueueEnableNotification)?;
} }
self.counters self.counters
@ -396,7 +411,7 @@ impl BlockEpollHandler {
.read_ops .read_ops
.fetch_add(read_ops.0, Ordering::AcqRel); .fetch_add(read_ops.0, Ordering::AcqRel);
Ok(used_descs) Ok(())
} }
fn signal_used_queue(&self) -> result::Result<(), DeviceError> { fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
@ -487,20 +502,19 @@ impl EpollHelperHandler for BlockEpollHandler {
EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {:?}", e)) EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {:?}", e))
})?; })?;
let needs_notification = self.process_queue_complete().map_err(|e| { self.process_queue_complete().map_err(|e| {
EpollHelperError::HandleEvent(anyhow!( EpollHelperError::HandleEvent(anyhow!(
"Failed to process queue (complete): {:?}", "Failed to process queue (complete): {:?}",
e e
)) ))
})?; })?;
if needs_notification { let rate_limit_reached =
self.signal_used_queue().map_err(|e| { self.rate_limiter.as_ref().map_or(false, |r| r.is_blocked());
EpollHelperError::HandleEvent(anyhow!(
"Failed to signal used queue: {:?}", // Process the queue only when the rate limit is not reached
e if !rate_limit_reached {
)) self.process_queue_submit_and_signal()?
})?;
} }
} }
RATE_LIMITER_EVENT => { RATE_LIMITER_EVENT => {
@ -606,7 +620,8 @@ impl Block {
| (1u64 << VIRTIO_BLK_F_FLUSH) | (1u64 << VIRTIO_BLK_F_FLUSH)
| (1u64 << VIRTIO_BLK_F_CONFIG_WCE) | (1u64 << VIRTIO_BLK_F_CONFIG_WCE)
| (1u64 << VIRTIO_BLK_F_BLK_SIZE) | (1u64 << VIRTIO_BLK_F_BLK_SIZE)
| (1u64 << VIRTIO_BLK_F_TOPOLOGY); | (1u64 << VIRTIO_BLK_F_TOPOLOGY)
| (1u64 << VIRTIO_RING_F_EVENT_IDX);
if iommu { if iommu {
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM; avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
@ -779,8 +794,12 @@ impl VirtioDevice for Block {
self.update_writeback(); self.update_writeback();
let mut epoll_threads = Vec::new(); let mut epoll_threads = Vec::new();
let event_idx = self.common.feature_acked(VIRTIO_RING_F_EVENT_IDX.into());
for i in 0..queues.len() { for i in 0..queues.len() {
let (_, queue, queue_evt) = queues.remove(0); let (_, mut queue, queue_evt) = queues.remove(0);
queue.set_event_idx(event_idx);
let queue_size = queue.size(); let queue_size = queue.size();
let (kill_evt, pause_evt) = self.common.dup_eventfds(); let (kill_evt, pause_evt) = self.common.dup_eventfds();
let queue_idx = i as u16; let queue_idx = i as u16;