-
Notifications
You must be signed in to change notification settings - Fork 84
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
macos: implement host->guest time sync
macOS can be quite aggressive when it comes to force apps to take long naps for power saving reasons. In addition to this, CNTVCT_EL0 doesn't increase during these naps, meaning the guest is not aware that's missing ticks. Both things combined mean that guests that run for long enough will suffer from clock skews. To address this, we introduce here a simple and lightweight host to guest time sync. From the host side, the VMM will send DGRAM packets via vsock with the current time at regular intervals (60 seconds) and when an oversleep (host thread sleeping 3 times more than expected) is detected. On the guest side, "init" spawns a process that listens for such packets and sets the system clock if a delta bigger than 100ms is detected. Fixes: #80 Signed-off-by: Sergio Lopez <[email protected]>
- Loading branch information
Showing
6 changed files
with
200 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
use std::sync::atomic::{AtomicUsize, Ordering}; | ||
use std::sync::{Arc, Mutex}; | ||
use std::thread; | ||
use std::time; | ||
|
||
use super::super::super::legacy::Gic; | ||
use super::super::Queue as VirtQueue; | ||
use super::super::VIRTIO_MMIO_INT_VRING; | ||
use super::defs::uapi; | ||
use super::packet::VsockPacket; | ||
|
||
use utils::eventfd::EventFd; | ||
use vm_memory::GuestMemoryMmap; | ||
|
||
const UPDATE_INTERVAL: u64 = 60 * 1000 * 1000 * 1000; | ||
const SLEEP_NSECS: u64 = 2 * 1000 * 1000 * 1000; | ||
const TSYNC_PORT: u32 = 123; | ||
|
||
pub struct TimesyncThread { | ||
cid: u64, | ||
mem: GuestMemoryMmap, | ||
queue_mutex: Arc<Mutex<VirtQueue>>, | ||
interrupt_evt: EventFd, | ||
interrupt_status: Arc<AtomicUsize>, | ||
intc: Option<Arc<Mutex<Gic>>>, | ||
irq_line: Option<u32>, | ||
} | ||
|
||
impl TimesyncThread { | ||
pub fn new( | ||
cid: u64, | ||
mem: GuestMemoryMmap, | ||
queue_mutex: Arc<Mutex<VirtQueue>>, | ||
interrupt_evt: EventFd, | ||
interrupt_status: Arc<AtomicUsize>, | ||
intc: Option<Arc<Mutex<Gic>>>, | ||
irq_line: Option<u32>, | ||
) -> Self { | ||
Self { | ||
cid, | ||
mem, | ||
queue_mutex, | ||
interrupt_evt, | ||
interrupt_status, | ||
intc, | ||
irq_line, | ||
} | ||
} | ||
|
||
fn send_time(&self, time: u64) { | ||
let mut queue = self.queue_mutex.lock().unwrap(); | ||
if let Some(head) = queue.pop(&self.mem) { | ||
if let Ok(mut pkt) = VsockPacket::from_rx_virtq_head(&head) { | ||
pkt.set_op(uapi::VSOCK_OP_RW) | ||
.set_src_cid(uapi::VSOCK_HOST_CID) | ||
.set_dst_cid(self.cid) | ||
.set_src_port(TSYNC_PORT) | ||
.set_dst_port(TSYNC_PORT) | ||
.set_type(uapi::VSOCK_TYPE_DGRAM); | ||
|
||
pkt.write_time_sync(time); | ||
pkt.set_len(pkt.buf().unwrap().len() as u32); | ||
queue.add_used(&self.mem, head.index, pkt.hdr().len() as u32 + pkt.len()); | ||
self.interrupt_status | ||
.fetch_or(VIRTIO_MMIO_INT_VRING as usize, Ordering::SeqCst); | ||
if let Some(intc) = &self.intc { | ||
intc.lock().unwrap().set_irq(self.irq_line.unwrap()); | ||
} else if let Err(e) = self.interrupt_evt.write(1) { | ||
warn!("failed to signal used queue: {:?}", e); | ||
} | ||
} | ||
} | ||
} | ||
|
||
fn work(&mut self) { | ||
let mut last_update = 0u64; | ||
let mut last_awake = utils::time::get_time(utils::time::ClockType::Real); | ||
loop { | ||
let now = utils::time::get_time(utils::time::ClockType::Real); | ||
/* | ||
* We send a time sync packet if we slept for 3 times more | ||
* nanoseconds than expected (which is an indication the | ||
* system forced us to take a long nap), or if UPDATE_INTERVAL | ||
* has been reached. | ||
*/ | ||
if (now - last_awake) >= (SLEEP_NSECS * 3) || (now - last_update) >= UPDATE_INTERVAL { | ||
self.send_time(now); | ||
last_update = now; | ||
} | ||
|
||
last_awake = utils::time::get_time(utils::time::ClockType::Real); | ||
thread::sleep(time::Duration::from_nanos(SLEEP_NSECS)); | ||
} | ||
} | ||
|
||
pub fn run(mut self) { | ||
thread::spawn(move || self.work()); | ||
} | ||
} |