除了virtio-mmio设备,firecracker还实现了串口、键盘等legacy设备,这些设备可以直接由虚拟机内核自带驱动程序驱动,并为虚拟机提供基本的输入输出功能。X86架构下,legacy设备主要通过IO端口进行控制和操作,因此firecracker采用IO总线来组织和管理所有的legacy设备。
运行时原理
和mmio总线类似,legacy设备也以BTree形式组织于devices::Bus下,并且每个设备都需要实现BusDevice trait:
firecracker/devices/src/legacy/i8042.rs:
pub struct I8042Device { // i8042为键盘对象
…
}
impl BusDevice for I8042Device { // 实现端口读写操作
fn read(&mut self, offset: u64, data: &mut [u8]) {
…
}
fn write(&mut self, offset: u64, data: &[u8]) {
…
}
}
firecracker/devices/src/legacy/serial.rs:
pub struct Serial { // 串口对象
…
}
impl BusDevice for Serial { // 实现端口读写操作
fn read(&mut self, offset: u64, data: &mut [u8]) {
…
}
fn write(&mut self, offset: u64, data: &[u8]) {
…
}
}
回顾前文分析CPU原理时介绍的内容,当虚拟CPU执行端口读写指令时(in/out指令),会退出到用户态VMM程序进行处理,这里就是firecracker的vCPU线程上下文。此时vCPU线程就会根据端口地址在IO总线上搜索对应的legacy设备,并调用设备对端口的读写操作接口。具体设备的操作方法大家可以对照相关规范文档展开代码分析。
初始化
firecracker使用LegacyDeviceManager来管理所有legacy设备,并通过其下register_devices方法来初始化legacy设备:
firecracker/vmm/src/lib.rs:
fn attach_legacy_devices(&mut self) -> std::result::Result<(), StartMicrovmError> { // legacy设备整体初始化函数
self.legacy_device_manager // 通过LegacyDeviceManager的register_devices来注册legacy设备
.register_devices()
.map_err(StartMicrovmError::LegacyIOBus)?;
self.vm
.get_fd()
.register_irqfd(self.legacy_device_manager.com_evt_1_3.as_raw_fd(), 4) // 为串口对应的eventfd申请irqfd
.map_err(|e| {
StartMicrovmError::LegacyIOBus(device_manager::legacy::Error::EventFd(e))
})?;
…
self.vm
.get_fd()
.register_irqfd(self.legacy_device_manager.kbd_evt.as_raw_fd(), 1) // 为键盘对应的eventfd申请irqfd
.map_err(|e| StartMicrovmError::LegacyIOBus(device_manager::legacy::Error::EventFd(e)))
}
firecracker/vmm/src/device_manager/legacy.rs:
pub struct LegacyDeviceManager { // 所有legacy设备管理器
pub io_bus: devices::Bus, // IO总线,以BTree组织legacy设备
pub stdio_serial: Arc<Mutex<devices::legacy::Serial>>, // 串口对象
pub i8042: Arc<Mutex<devices::legacy::I8042Device>>, // 键盘对象
pub com_evt_1_3: EventFd, // 串口1、3对应的eventfd
pub com_evt_2_4: EventFd, // 串口2、4对应的eventfd
pub kbd_evt: EventFd, // 键盘对应的eventfd
pub stdin_handle: io::Stdin, // 标准输入
}
impl LegacyDeviceManager {
…
pub fn register_devices(&mut self) -> Result<()> {
self.io_bus // 将串口对象添加到端口地址0x3F8~0x3FF,
.insert(self.stdio_serial.clone(), 0x3f8, 0x8) // 由规范定义
.map_err(Error::BusError)?;
…
self.io_bus // 将键盘对象添加到端口地址0x60~0x64 ,
.insert(self.i8042.clone(), 0x060, 0x5) // 由规范定义
.map_err(Error::BusError)?;
Ok(())
}
}
转载请注明:吴斌的博客 » 【firecracker】legacy设备