I observed hangs in the following situation when using Cloud Hypervisor with virtiofsd: 1. Start virtiofsd 2. Start Cloud Hypervisor instance 1, connected to virtiofsd. 3. Start Cloud Hypervisor instance 2, waiting for migration. 4. Migrate VM from Cloud Hypervisor instance 1 to 2. 5. Start virtiofsd again. The hangs happened because Cloud Hypervisor remembered, as part of the migration data, which vhost-user protocol features the backend for its fs device supported. Instance 2 therefore never sent GET_PROTOCOL_FEATURES to the second invocation of virtiofsd. This should work, but it didn't, because update_reply_ack_flag() checked whether self.protocol_features contained GET_PROTOCOL_FEATURES, but self.protocol_features is only filled in when GET_PROTOCOL_FEATURES is called. As a result, Cloud Hypervisor expected a reply that virtiofsd would never send. Since REPLY_ACK is handled entirely by the vhost-user library, and not by the backend, there's no need to ask the backend whether it supports REPLY_ACK in the first place, so we can just drop the check for that from update_reply_ack_flag(). We know that we always support it, so we just need to check whether the backend has acked it. This fixes the hang described above. Since we will now always reply if the backend acks the feature, REPLY_ACK is now always included in the set of features returned by GET_PROTOCOL_FEATURES, just like with XEN_MMAP (when enabled). Signed-off-by: Alyssa Ross <hi@alyssa.is> |
||
|---|---|---|
| .. | ||
| src | ||
| tests | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| README.md | ||
vhost-user-backend
Design
The vhost-user-backend crate provides a framework to implement vhost-user backend services,
which includes following external public APIs:
- A daemon control object (
VhostUserDaemon) to start and stop the service daemon. - A vhost-user backend trait (
VhostUserBackendMut) to handle vhost-user control messages and virtio messages. - A vring access trait (
VringT) to access virtio queues, and three implementations of the trait:VringState,VringMutexandVringRwLock.
Usage
The vhost-user-backend crate provides a framework to implement vhost-user backend services. The main interface provided by vhost-user-backend library is the struct VhostUserDaemon:
pub struct VhostUserDaemon<S, V, B = ()>
where
S: VhostUserBackend<V, B>,
V: VringT<GM<B>> + Clone + Send + Sync + 'static,
B: Bitmap + 'static,
{
pub fn new(name: String, backend: S, atomic_mem: GuestMemoryAtomic<GuestMemoryMmap<B>>) -> Result<Self>;
pub fn start(&mut self, listener: Listener) -> Result<()>;
pub fn wait(&mut self) -> Result<()>;
pub fn get_epoll_handlers(&self) -> Vec<Arc<VringEpollHandler<S, V, B>>>;
}
Create a VhostUserDaemon Instance
The VhostUserDaemon::new() creates an instance of VhostUserDaemon object. The client needs to
pass in an VhostUserBackend object, which will be used to configure the VhostUserDaemon
instance, handle control messages from the vhost-user frontend and handle virtio requests from
virtio queues. A group of working threads will be created to handle virtio requests from configured
virtio queues.
Start the VhostUserDaemon
The VhostUserDaemon::start() method waits for an incoming connection from the vhost-user frontends
on the listener. Once a connection is ready, a main thread will be created to handle vhost-user
messages from the vhost-user frontend.
Stop the VhostUserDaemon
The VhostUserDaemon::stop() method waits for the main thread to exit. An exit event must be sent
to the main thread by writing to the exit_event EventFd before waiting for it to exit.
Threading Model
The main thread and virtio queue working threads will concurrently access the underlying virtio queues, so all virtio queue in multi-threading model. But the main thread only accesses virtio queues for configuration, so client could adopt locking policies to optimize for the virtio queue working threads.
Example
Example code to handle virtio messages from a virtio queue:
impl VhostUserBackendMut for VhostUserService {
fn process_queue(&mut self, vring: &VringMutex) -> Result<bool> {
let mut used_any = false;
let mem = match &self.mem {
Some(m) => m.memory(),
None => return Err(Error::NoMemoryConfigured),
};
let mut vring_state = vring.get_mut();
while let Some(avail_desc) = vring_state
.get_queue_mut()
.iter()
.map_err(|_| Error::IterateQueue)?
.next()
{
// Process the request...
if self.event_idx {
if vring_state.add_used(head_index, 0).is_err() {
warn!("Couldn't return used descriptors to the ring");
}
match vring_state.needs_notification() {
Err(_) => {
warn!("Couldn't check if queue needs to be notified");
vring_state.signal_used_queue().unwrap();
}
Ok(needs_notification) => {
if needs_notification {
vring_state.signal_used_queue().unwrap();
}
}
}
} else {
if vring_state.add_used(head_index, 0).is_err() {
warn!("Couldn't return used descriptors to the ring");
}
vring_state.signal_used_queue().unwrap();
}
}
Ok(used_any)
}
}
Postcopy support
To enabled POSTCOPY_* messages support there is a postcopy feature.
Due to how Xen handles memory mappings the postcopy feature is not compatible
with xen feature. Enabling both at the same time will result in a compilation error.
postcopy feature enables optional userfaultfd dependency in order to create and
interact with userfaultfd object. This requires access permission to /dev/userfaultfd
file from the backend.
Xen support
Supporting Xen requires special handling while mapping the guest memory. The
vm-memory crate implements xen memory mapping support via a separate feature
xen, and this crate uses the same feature name to enable Xen support.
Also, for xen mappings, the memory regions passed by the frontend contains few extra fields as described in the vhost-user protocol documentation.
It was decided by the rust-vmm maintainers to keep the interface simple and
build the crate for either standard Unix memory mapping or Xen, and not both.
License
This project is licensed under
- Apache License, Version 2.0