mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
block: use BLKDISCARD/BLKZEROOUT ioctls for block devices
Some block devices (ZFS volume) may require BLKDISCARD and BLKZEROOUT ioctls for discard and write_zeroes operations respectively. There is no good way to probe whether fallocate is supported on a block device. Arguably, punch_hole and write_zeroes are rare. Instead of having a complex scheme for the IO uring backend, we force it to always use ioctls. The code can be changed if the synchronized ioctls become a performance issue. Changes: - Detect block devices at construction time - Use BLKDISCARD ioctl for punch_hole (discard) on block devices - Use BLKZEROOUT ioctl for write_zeroes on block devices - Add BLKDISCARD/BLKZEROOUT to VirtioBlock seccomp whitelist - Keep fallocate() path for regular files (no behavior change) - Consolidate some helper functions to the new sparse module Signed-off-by: Wei Liu <liuwe@microsoft.com>
This commit is contained in:
@@ -11,13 +11,15 @@ use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult};
|
||||
use crate::error::{BlockError, BlockErrorKind, BlockResult};
|
||||
use crate::{BatchRequest, RequestType, SECTOR_SIZE};
|
||||
use crate::sparse::{blkdiscard, blkzeroout};
|
||||
use crate::{BatchRequest, RequestType, SECTOR_SIZE, is_block_device};
|
||||
|
||||
pub struct RawFileAsync {
|
||||
fd: RawFd,
|
||||
io_uring: IoUring,
|
||||
eventfd: EventFd,
|
||||
alignment: u64,
|
||||
is_block_device: bool,
|
||||
}
|
||||
|
||||
impl RawFileAsync {
|
||||
@@ -34,13 +36,32 @@ impl RawFileAsync {
|
||||
.register_eventfd(eventfd.as_raw_fd())
|
||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
|
||||
|
||||
let is_block_device = is_block_device(fd);
|
||||
|
||||
Ok(RawFileAsync {
|
||||
fd,
|
||||
io_uring,
|
||||
eventfd,
|
||||
alignment: SECTOR_SIZE,
|
||||
is_block_device,
|
||||
})
|
||||
}
|
||||
|
||||
/// Queue an `IORING_OP_NOP` carrying `user_data` so a synchronously
|
||||
/// completed operation (e.g. a BLK* ioctl) is reaped through the normal
|
||||
/// io_uring completion path.
|
||||
fn submit_nop(&mut self, user_data: u64) -> Result<(), Error> {
|
||||
let (submitter, mut sq, _) = self.io_uring.split();
|
||||
// SAFETY: Nop carries no buffer; only `user_data` is consumed by the
|
||||
// kernel.
|
||||
unsafe {
|
||||
sq.push(&opcode::Nop::new().build().user_data(user_data))
|
||||
.map_err(|e| Error::other(format!("Submission queue is full: {e:?}")))?;
|
||||
};
|
||||
sq.sync();
|
||||
submitter.submit()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncIo for RawFileAsync {
|
||||
@@ -235,6 +256,18 @@ impl AsyncIo for RawFileAsync {
|
||||
}
|
||||
|
||||
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
||||
// Some block devices don't support fallocate(). Use ioctl instead. The assumption is that
|
||||
// this happens rarely and we don't need to introduce unnecessary complexity by submitting
|
||||
// a fallocate request, reaping ENOTSUPP in the completion routine, and reissuing the
|
||||
// request with an ioctl.
|
||||
if self.is_block_device {
|
||||
blkdiscard(self.fd, offset, length).map_err(AsyncIoError::PunchHole)?;
|
||||
// Deliver the completion through the normal io_uring path by
|
||||
// queuing a NOP carrying `user_data`. The registered eventfd will
|
||||
// fire when it completes, just like any other request.
|
||||
return self.submit_nop(user_data).map_err(AsyncIoError::PunchHole);
|
||||
}
|
||||
|
||||
let (submitter, mut sq, _) = self.io_uring.split();
|
||||
|
||||
let mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE;
|
||||
@@ -260,6 +293,14 @@ impl AsyncIo for RawFileAsync {
|
||||
}
|
||||
|
||||
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
||||
// Same rationale as punch_hole().
|
||||
if self.is_block_device {
|
||||
blkzeroout(self.fd, offset, length).map_err(AsyncIoError::WriteZeroes)?;
|
||||
return self
|
||||
.submit_nop(user_data)
|
||||
.map_err(AsyncIoError::WriteZeroes);
|
||||
}
|
||||
|
||||
let (submitter, mut sq, _) = self.io_uring.split();
|
||||
|
||||
let mode = FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE;
|
||||
|
||||
Reference in New Issue
Block a user