From d9a355f85a9e1eda2d9f03d18050f8f2bf9a534b Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 9 Aug 2019 08:56:10 +0100 Subject: [PATCH] vmm: Add new "null" serial/console output mode Poor performance was observed when booting kernels with "console=ttyS0" and the serial port disabled. This change introduces a "null" console output mode and makes it the default for the serial console. In this case the serial port is advertised as per other output modes but there is no input and any output is dropped. Fixes: #163 Signed-off-by: Rob Bradford --- devices/src/legacy/serial.rs | 2 +- src/main.rs | 126 +++++++++++++++++++++++++++++++++-- vmm/src/config.rs | 6 ++ vmm/src/vm.rs | 11 +-- 4 files changed, 135 insertions(+), 10 deletions(-) diff --git a/devices/src/legacy/serial.rs b/devices/src/legacy/serial.rs index 577bf4d5d..63aa7e3d7 100644 --- a/devices/src/legacy/serial.rs +++ b/devices/src/legacy/serial.rs @@ -67,7 +67,7 @@ pub struct Serial { } impl Serial { - fn new(interrupt: Box, out: Option>) -> Serial { + pub fn new(interrupt: Box, out: Option>) -> Serial { Serial { interrupt_enable: 0, interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION, diff --git a/src/main.rs b/src/main.rs index 302817202..39ccef781 100755 --- a/src/main.rs +++ b/src/main.rs @@ -137,13 +137,13 @@ fn main() { .arg( Arg::with_name("serial") .long("serial") - .help("Control serial port: off|tty|file=/path/to/a/file") - .default_value("off"), + .help("Control serial port: off|null|tty|file=/path/to/a/file") + .default_value("null"), ) .arg( Arg::with_name("console") .long("console") - .help("Control (virtio) console: off|tty|file=/path/to/a/file") + .help("Control (virtio) console: off|null|tty|file=/path/to/a/file") .default_value("tty"), ) .arg( @@ -1322,7 +1322,7 @@ mod tests { } #[test] - fn test_serial_disable() { + fn test_serial_off() { test_block!(tb, "", { let mut clear = ClearDiskConfig::new(); let guest = Guest::new(&mut clear); @@ -1380,6 +1380,124 @@ mod tests { }); } + #[test] + fn test_serial_null() { + test_block!(tb, "", { + let mut clear = ClearDiskConfig::new(); + let guest = Guest::new(&mut clear); + let mut child = Command::new("target/debug/cloud-hypervisor") + .args(&["--cpus", "1"]) + .args(&["--memory", "size=512M"]) + .args(&["--kernel", guest.fw_path.as_str()]) + .args(&[ + "--disk", + guest + .disk_config + .disk(DiskType::OperatingSystem) + .unwrap() + .as_str(), + guest + .disk_config + .disk(DiskType::CloudInit) + .unwrap() + .as_str(), + ]) + .args(&["--net", guest.default_net_string().as_str()]) + .args(&["--serial", "null"]) + .args(&["--console", "off"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + + thread::sleep(std::time::Duration::new(20, 0)); + + // Test that there is a ttyS0 + aver_eq!( + tb, + guest + .ssh_command("cat /proc/interrupts | grep 'IO-APIC' | grep -c 'ttyS0'") + .trim() + .parse::() + .unwrap(), + 1 + ); + + guest.ssh_command("sudo reboot"); + thread::sleep(std::time::Duration::new(10, 0)); + let _ = child.kill(); + match child.wait_with_output() { + Ok(out) => { + aver!( + tb, + !String::from_utf8_lossy(&out.stdout).contains("cloud login:") + ); + } + Err(_) => aver!(tb, false), + } + Ok(()) + }); + } + + #[test] + fn test_serial_tty() { + test_block!(tb, "", { + let mut clear = ClearDiskConfig::new(); + let guest = Guest::new(&mut clear); + let mut child = Command::new("target/debug/cloud-hypervisor") + .args(&["--cpus", "1"]) + .args(&["--memory", "size=512M"]) + .args(&["--kernel", guest.fw_path.as_str()]) + .args(&[ + "--disk", + guest + .disk_config + .disk(DiskType::OperatingSystem) + .unwrap() + .as_str(), + guest + .disk_config + .disk(DiskType::CloudInit) + .unwrap() + .as_str(), + ]) + .args(&["--net", guest.default_net_string().as_str()]) + .args(&["--serial", "tty"]) + .args(&["--console", "off"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + + thread::sleep(std::time::Duration::new(20, 0)); + + // Test that there is a ttyS0 + aver_eq!( + tb, + guest + .ssh_command("cat /proc/interrupts | grep 'IO-APIC' | grep -c 'ttyS0'") + .trim() + .parse::() + .unwrap(), + 1 + ); + + guest.ssh_command("sudo reboot"); + thread::sleep(std::time::Duration::new(10, 0)); + let _ = child.kill(); + match child.wait_with_output() { + Ok(out) => { + aver!( + tb, + String::from_utf8_lossy(&out.stdout).contains("cloud login:") + ); + } + Err(_) => aver!(tb, false), + } + Ok(()) + }); + } + #[test] fn test_serial_file() { test_block!(tb, "", { diff --git a/vmm/src/config.rs b/vmm/src/config.rs index f40458274..eb07647ab 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -350,6 +350,7 @@ pub enum ConsoleOutputMode { Off, Tty, File, + Null, } impl ConsoleOutputMode { @@ -383,6 +384,11 @@ impl<'a> ConsoleConfig<'a> { mode: ConsoleOutputMode::File, file: Some(Path::new(¶m[5..])), }) + } else if param.starts_with("null") { + Ok(Self { + mode: ConsoleOutputMode::Null, + file: None, + }) } else { Err(Error::ParseConsoleParam) } diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index f1381eb99..6508227d3 100755 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -40,7 +40,7 @@ use pci::{ use qcow::{self, ImageType, QcowFile}; use std::ffi::CString; use std::fs::{File, OpenOptions}; -use std::io::{self, stdout}; +use std::io::{self, sink, stdout}; use std::os::unix::fs::OpenOptionsExt; use std::os::unix::io::{AsRawFd, RawFd}; use std::ptr::null_mut; @@ -561,9 +561,9 @@ impl DeviceManager { .map_err(DeviceManagerError::SerialOutputFileOpen)?, )), ConsoleOutputMode::Tty => Some(Box::new(stdout())), - ConsoleOutputMode::Off => None, + ConsoleOutputMode::Off | ConsoleOutputMode::Null => None, }; - let serial = if serial_writer.is_some() { + let serial = if vm_info.vm_cfg.serial.mode != ConsoleOutputMode::Off { // Serial is tied to IRQ #4 let serial_irq = 4; let interrupt: Box = if let Some(ioapic) = &ioapic { @@ -578,9 +578,9 @@ impl DeviceManager { Box::new(KernelIoapicIrq::new(serial_evt)) }; - Some(Arc::new(Mutex::new(devices::legacy::Serial::new_out( + Some(Arc::new(Mutex::new(devices::legacy::Serial::new( interrupt, - serial_writer.unwrap(), + serial_writer, )))) } else { None @@ -601,6 +601,7 @@ impl DeviceManager { .map_err(DeviceManagerError::ConsoleOutputFileOpen)?, )), ConsoleOutputMode::Tty => Some(Box::new(stdout())), + ConsoleOutputMode::Null => Some(Box::new(sink())), ConsoleOutputMode::Off => None, }; let console = if console_writer.is_some() {