Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add no console option #40

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ fn main() -> Result<(), Error> {
opts.cpus,
opts.memory,
&opts.kernel,
opts.console,
opts.console.clone(),
opts.initramfs,
opts.net,
)
Expand Down
4 changes: 2 additions & 2 deletions src/vmm/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

131 changes: 66 additions & 65 deletions src/vmm/src/cpu/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause

use std::convert::TryInto;
use std::io;
use std::io::Write;
use std::os::unix::net::UnixStream;
use std::sync::{Arc, Mutex};
use std::{result, u64};

Expand All @@ -11,7 +12,6 @@ use kvm_ioctls::{VcpuExit, VcpuFd, VmFd};
use vm_device::bus::MmioAddress;
use vm_device::device_manager::{IoManager, MmioManager};
use vm_memory::{Address, Bytes, GuestAddress, GuestMemoryError, GuestMemoryMmap};
use vmm_sys_util::terminal::Terminal;

use crate::devices::serial::{LumperSerial, SERIAL_PORT_BASE, SERIAL_PORT_LAST_REGISTER};

Expand Down Expand Up @@ -224,84 +224,85 @@ impl Vcpu {
}

/// vCPU emulation loop.
pub fn run(&mut self) {
pub fn run(&mut self, socket_name: String) {
let mut unix_socket = UnixStream::connect(socket_name).unwrap();
// Call into KVM to launch (VMLAUNCH) or resume (VMRESUME) the virtual CPU.
// This is a blocking function, it only returns for either an error or a
// VM-Exit. In the latter case, we can inspect the exit reason.
match self.vcpu_fd.run() {
Ok(exit_reason) => match exit_reason {
// The VM stopped (Shutdown ot HLT).
VcpuExit::Shutdown | VcpuExit::Hlt => {
println!("Guest shutdown: {:?}. Bye!", exit_reason);
let stdin = io::stdin();
let stdin_lock = stdin.lock();
stdin_lock.set_canon_mode().unwrap();

unsafe { libc::exit(0) };
}
loop {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we move the loop here to avoid passing the socket_name after each vm exit?

let run = self.vcpu_fd.run();

match run {
Ok(exit_reason) => match exit_reason {
// The VM stopped (Shutdown ot HLT).
VcpuExit::Shutdown | VcpuExit::Hlt => {
println!("Guest shutdown: {:?}. Bye!", exit_reason);
unix_socket.write_all(b"1").unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We notify the main thread and then continue running?

}

// This is a PIO write, i.e. the guest is trying to write
// something to an I/O port.
VcpuExit::IoOut(addr, data) => match addr {
SERIAL_PORT_BASE..=SERIAL_PORT_LAST_REGISTER => {
self.serial
.lock()
.unwrap()
.serial
.write(
// This is a PIO write, i.e. the guest is trying to write
// something to an I/O port.
VcpuExit::IoOut(addr, data) => match addr {
SERIAL_PORT_BASE..=SERIAL_PORT_LAST_REGISTER => {
self.serial
.lock()
.unwrap()
.serial
.write(
(addr - SERIAL_PORT_BASE)
.try_into()
.expect("Invalid serial register offset"),
data[0],
)
.unwrap();
}
_ => {
println!("Unsupported device write at {:x?}", addr);
}
},

// This is a PIO read, i.e. the guest is trying to read
// from an I/O port.
VcpuExit::IoIn(addr, data) => match addr {
SERIAL_PORT_BASE..=SERIAL_PORT_LAST_REGISTER => {
data[0] = self.serial.lock().unwrap().serial.read(
(addr - SERIAL_PORT_BASE)
.try_into()
.expect("Invalid serial register offset"),
data[0],
)
);
}
_ => {
println!("Unsupported device read at {:x?}", addr);
}
},

// This is a MMIO write, i.e. the guest is trying to write
// something to a memory-mapped I/O region.
VcpuExit::MmioWrite(addr, data) => {
self.virtio_manager
.lock()
.unwrap()
.mmio_write(MmioAddress(addr), data)
.unwrap();
}
_ => {
println!("Unsupported device write at {:x?}", addr);
}
},

// This is a PIO read, i.e. the guest is trying to read
// from an I/O port.
VcpuExit::IoIn(addr, data) => match addr {
SERIAL_PORT_BASE..=SERIAL_PORT_LAST_REGISTER => {
data[0] = self.serial.lock().unwrap().serial.read(
(addr - SERIAL_PORT_BASE)
.try_into()
.expect("Invalid serial register offset"),
);
// This is a MMIO read, i.e. the guest is trying to read
// from a memory-mapped I/O region.
VcpuExit::MmioRead(addr, data) => {
self.virtio_manager
.lock()
.unwrap()
.mmio_read(MmioAddress(addr), data)
.unwrap();
}

_ => {
println!("Unsupported device read at {:x?}", addr);
eprintln!("Unhandled VM-Exit: {:?}", exit_reason);
}
},

// This is a MMIO write, i.e. the guest is trying to write
// something to a memory-mapped I/O region.
VcpuExit::MmioWrite(addr, data) => {
self.virtio_manager
.lock()
.unwrap()
.mmio_write(MmioAddress(addr), data)
.unwrap();
}

// This is a MMIO read, i.e. the guest is trying to read
// from a memory-mapped I/O region.
VcpuExit::MmioRead(addr, data) => {
self.virtio_manager
.lock()
.unwrap()
.mmio_read(MmioAddress(addr), data)
.unwrap();
}

_ => {
eprintln!("Unhandled VM-Exit: {:?}", exit_reason);
}
},

Err(e) => eprintln!("Emulation error: {}", e),
Err(e) => eprintln!("Emulation error: {}", e),
}
}
}
}
2 changes: 1 addition & 1 deletion src/vmm/src/kernel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const HIMEM_START: u64 = 0x0010_0000; // 1 MB
/// Address where the kernel command line is written.
const CMDLINE_START: u64 = 0x0002_0000;
// Default command line
pub const DEFAULT_CMDLINE: &str = "console=ttyS0 i8042.nokbd reboot=k panic=1 pci=off";
pub const DEFAULT_CMDLINE: &str = "i8042.nokbd reboot=k panic=1 pci=off";

fn add_e820_entry(
params: &mut boot_params,
Expand Down
58 changes: 55 additions & 3 deletions src/vmm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ extern crate linux_loader;
extern crate vm_memory;
extern crate vm_superio;

use std::any::Any;
use std::fs::File;
use std::io::stdout;
use std::os::unix::io::AsRawFd;
use std::os::unix::net::UnixListener;
use std::os::unix::prelude::RawFd;
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::thread;
use std::{io, path::PathBuf};
Expand All @@ -25,6 +28,7 @@ use vm_device::device_manager::IoManager;
use vm_device::resources::Resource;
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion};
use vmm_sys_util::eventfd::EventFd;
use vmm_sys_util::rand;
use vmm_sys_util::terminal::Terminal;
mod cpu;
use cpu::{cpuid, mptable, Vcpu};
Expand Down Expand Up @@ -86,6 +90,10 @@ pub enum Error {
VirtioNet(devices::net::VirtioNetError),
/// Error related to IOManager.
IoManager(vm_device::device_manager::Error),
/// Access thread handler error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does that mean?

AccessThreadHandlerError,
/// Join thread error
JoinThreadError(Box<dyn Any + Send>),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unused.

}

/// Dedicated [`Result`](https://doc.rust-lang.org/std/result/) type.
Expand Down Expand Up @@ -262,6 +270,14 @@ impl VMM {
}

pub fn configure_console(&mut self, console_path: Option<String>) -> Result<()> {
if console_path.is_none() {
return Ok(());
}

self.cmdline
.insert_str("console=ttyS0")
.map_err(Error::Cmdline)?;

if let Some(console_path) = console_path {
// We create the file if it does not exist, else we open
let file = File::create(&console_path).map_err(Error::ConsoleError)?;
Expand Down Expand Up @@ -326,13 +342,41 @@ impl VMM {

// Run all virtual CPUs.
pub fn run(&mut self) -> Result<()> {
let mut unix_socket_name = String::from("/tmp/vmm.sock");
while Path::new(&unix_socket_name).exists() {
let rng = rand::rand_alphanumerics(8);
unix_socket_name = format!("/tmp/vmm-{}.sock", rng.to_str().unwrap());
}
Comment on lines +345 to +349
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iiuc you're passing a named unix socket to the vcpu for it to check in when it get a shutdown/halt?
A simpler, more idiomatic approach is mpsc. And unless I'm missing why it would not work, I'd prefer that you use it. And yes, you may need to run a monitoring thread.


let mut handlers: Vec<thread::JoinHandle<_>> = Vec::new();
let listener = UnixListener::bind(unix_socket_name.as_str()).unwrap();
let total_cpus = self.vcpus.len();

for mut vcpu in self.vcpus.drain(..) {
println!("Starting vCPU {:?}", vcpu.index);
let _ = thread::Builder::new().spawn(move || loop {
vcpu.run();
let socket_name = unix_socket_name.clone();
let handler = thread::Builder::new().spawn(move || {
vcpu.run(socket_name.clone());
});

match handler {
Ok(handler) => handlers.push(handler),
Err(_) => {
println!("Failed to start vCPU");
return Err(Error::AccessThreadHandlerError);
}
}
}

let mut connections: Vec<_> = Vec::new();

while connections.len() < total_cpus {
let connection = listener.accept().unwrap().0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please handle errors properly, by propagating them.

self.epoll.add_fd(connection.as_raw_fd()).unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto.

connections.push(connection);
Comment on lines +372 to +375
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be a good place to explain what you're trying to do here: You're going to listen on a socket for any vcpu to halt and then do something about that information.

}

self.epoll.add_fd(listener.as_raw_fd()).unwrap();

let stdin = io::stdin();
let stdin_lock = stdin.lock();
stdin_lock
Expand Down Expand Up @@ -383,6 +427,14 @@ impl VMM {
.process_tap()
.map_err(Error::VirtioNet)?;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we still listening to stdin when the VM has no console?

if connections.iter().any(|c| c.as_raw_fd() == event_data) {
use vmm_sys_util::signal::Killable;
handlers.iter().for_each(|handler| {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we're killing each thread whenever ones is shutdown/halted, right?

handler.kill(9).unwrap();
});
return Ok(());
}
}
}
}
Expand Down