vmm: Shutdown vCPU threads

As part of the cleanup of the VM shutdown all the vCPU threads. This is
achieved by toggling a shared atomic boolean variable which is checked
in the vCPU loop. To trigger the vCPU code to look at this boolean it is
necessary to send a signal to the vCPU which will interrupt the running
KVM_RUN ioctl.

Fixes: #229

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
This commit is contained in:
Rob Bradford
2019-09-03 10:00:15 +01:00
committed by Samuel Ortiz
parent 40f9da524f
commit bb2e7bb942
+31 -1
View File
@@ -47,6 +47,7 @@ use std::ops::Deref;
use std::os::unix::fs::OpenOptionsExt; use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::ptr::null_mut; use std::ptr::null_mut;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Barrier, Mutex, RwLock}; use std::sync::{Arc, Barrier, Mutex, RwLock};
use std::{fmt, result, str, thread}; use std::{fmt, result, str, thread};
use vfio::{VfioDevice, VfioPciDevice, VfioPciError}; use vfio::{VfioDevice, VfioPciDevice, VfioPciError};
@@ -59,7 +60,7 @@ use vm_memory::{
use vm_virtio::transport::VirtioPciDevice; use vm_virtio::transport::VirtioPciDevice;
use vm_virtio::{VirtioSharedMemory, VirtioSharedMemoryList}; use vm_virtio::{VirtioSharedMemory, VirtioSharedMemoryList};
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
use vmm_sys_util::signal::register_signal_handler; use vmm_sys_util::signal::{register_signal_handler, validate_signal_num};
use vmm_sys_util::terminal::Terminal; use vmm_sys_util::terminal::Terminal;
const VCPU_RTSIG_OFFSET: i32 = 0; const VCPU_RTSIG_OFFSET: i32 = 0;
@@ -225,6 +226,9 @@ pub enum Error {
/// Cannot spawn a signal handler thread /// Cannot spawn a signal handler thread
SignalHandlerSpawn(io::Error), SignalHandlerSpawn(io::Error),
/// Failed to join on vCPU threads
ThreadCleanup,
} }
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;
@@ -1401,6 +1405,7 @@ pub struct Vm<'a> {
epoll: EpollContext, epoll: EpollContext,
on_tty: bool, on_tty: bool,
creation_ts: std::time::Instant, creation_ts: std::time::Instant,
vcpus_kill_signalled: Arc<AtomicBool>,
} }
impl<'a> Vm<'a> { impl<'a> Vm<'a> {
@@ -1625,6 +1630,7 @@ impl<'a> Vm<'a> {
epoll, epoll,
on_tty, on_tty,
creation_ts, creation_ts,
vcpus_kill_signalled: Arc::new(AtomicBool::new(false)),
}) })
} }
@@ -1793,6 +1799,24 @@ impl<'a> Vm<'a> {
.map_err(Error::SetTerminalCanon)?; .map_err(Error::SetTerminalCanon)?;
} }
// Tell the vCPUs to stop themselves next time they go through the loop
self.vcpus_kill_signalled.store(true, Ordering::SeqCst);
// Signal to the vCPU threads. This will interrupt the KVM_RUN ioctl() allowing
// the loop to check the boolean set above.
for vcpu in self.vcpus.iter() {
let signum = validate_signal_num(VCPU_RTSIG_OFFSET, true).unwrap();
unsafe {
use std::os::unix::thread::JoinHandleExt;
libc::pthread_kill(vcpu.as_pthread_t(), signum);
}
}
// Wait for all the vCPU threads to finish
for vcpu in self.vcpus.drain(..) {
vcpu.join().map_err(|_| Error::ThreadCleanup)?
}
Ok(exit_behaviour) Ok(exit_behaviour)
} }
@@ -1828,6 +1852,7 @@ impl<'a> Vm<'a> {
let vcpu_thread_barrier = vcpu_thread_barrier.clone(); let vcpu_thread_barrier = vcpu_thread_barrier.clone();
let reset_evt = self.devices.reset_evt.try_clone().unwrap(); let reset_evt = self.devices.reset_evt.try_clone().unwrap();
let vcpu_kill_signalled = self.vcpus_kill_signalled.clone();
self.vcpus.push( self.vcpus.push(
thread::Builder::new() thread::Builder::new()
.name(format!("vcpu{}", vcpu.id)) .name(format!("vcpu{}", vcpu.id))
@@ -1861,6 +1886,11 @@ impl<'a> Vm<'a> {
break; break;
} }
} }
// We've been told to terminate
if vcpu_kill_signalled.load(Ordering::SeqCst) {
break;
}
} }
}) })
.map_err(Error::VcpuSpawn)?, .map_err(Error::VcpuSpawn)?,