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 <robert.bradford@intel.com>
This commit is contained in:
Rob Bradford 2019-08-09 08:56:10 +01:00 committed by Sebastien Boeuf
parent f910476dd7
commit d9a355f85a
4 changed files with 135 additions and 10 deletions

View File

@ -67,7 +67,7 @@ pub struct Serial {
}
impl Serial {
fn new(interrupt: Box<Interrupt>, out: Option<Box<io::Write + Send>>) -> Serial {
pub fn new(interrupt: Box<Interrupt>, out: Option<Box<io::Write + Send>>) -> Serial {
Serial {
interrupt_enable: 0,
interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION,

View File

@ -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::<u32>()
.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::<u32>()
.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, "", {

View File

@ -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(&param[5..])),
})
} else if param.starts_with("null") {
Ok(Self {
mode: ConsoleOutputMode::Null,
file: None,
})
} else {
Err(Error::ParseConsoleParam)
}

View File

@ -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<devices::Interrupt> = 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() {