在分析完firecracker虚拟机中各个子系统的运行时原理和初始化流程后,最后我们整体分析一下firecracker的系统启动过程,并重点分析IO线程(fc_vmm)所采用的epoll事件循环框架。
回顾首文中介绍的firecracker进程的线程模型,下图展示了线程间的事件通知关系:
firecracker进程的主线程在启动完成后会成为API Server Thread,它通过进程间socket句柄与外部HTTP Client进行通信,接收外部程序发送的配置或控制命令(回顾一下使用方法);API Server Thread收到外部命令后通过api_event_fd向IO Thread发送请求,由IO Thread完成实际的处理动作;当虚拟机正常启动后,IO Thread主要通过io_event_fd接受来自虚拟CPU的IO请求,并在完成IO请求后通过irq_fd通知CPU处理结果。
接下来,我们从main函数开始,逐步分析一下系统的启动过程。
firecracker/src/main.rs:
fn main() {
…
let shared_info = Arc::new(RwLock::new(InstanceInfo { // InstanceInfo对象是主线程和fc_vmm线程之间的共享对象
// Arc是Rust标准库提供的原子引用类型,跟踪多个线程对相同
// 对象的引用计数;RwLock是Rust标准库提供的读写锁
state: InstanceState::Uninitialized,
id: instance_id,
vmm_version: crate_version!().to_string(),
}));
let mmds_info = MMDS.clone();
let (to_vmm, from_api) = channel(); // 该channel对象用于在主线程和fc_vmm线程之间传递对象
// to_vmm为发送端对象,在主线程中使用;from_api为接收
// 端,在fc_vmm线程中使用
let server =
ApiServer::new(mmds_info, shared_info.clone(), to_vmm) // 创建一个ApiServer对象,用于接收外部程序的HTTP请求
.expect("Cannot create API server");
let api_event_fd = server // 申请一个event_fd,用于主线程和fc_vmm线程之间的事件通知
.get_event_fd_clone()
.expect("Cannot clone API eventFD.");
let _vmm_thread_handle =
vmm::start_vmm_thread( // 正式拉起fc_vmm线程,下文将进一步分析
shared_info,
api_event_fd,
from_api,
seccomp_level);
match server.bind_and_run( // 主线程开始作为API Server线程,监听socket请求
bind_path, start_time_us,
start_time_cpu_us, seccomp_level) {
…
}
}
firecracker/vmm/src/lib.rs:
pub fn start_vmm_thread(
api_shared_info: Arc<RwLock<InstanceInfo>>,
api_event_fd: EventFd,
from_api: Receiver<Box<VmmAction>>,
seccomp_level: u32,
) -> thread::JoinHandle<()> {
thread::Builder::new()
.name("fc_vmm".to_string()) // 将新线程取名为fc_vmm
.spawn(move || { // 创建新线程,参数为新线程入口函数(闭包)
let mut vmm = Vmm::new(api_shared_info, api_event_fd, from_api, seccomp_level) // 新线程首先创建全局Vmm对象
.expect("Cannot create VMM");
match vmm.run_control() { // 进入epoll事件循环
…
}
})
.expect("VMM thread spawn failed.")
}
struct Vmm { // Vmm全局对象
kvm: KvmContext, // KVM操作上下文
vm_config: VmConfig, // 虚拟机配置,如CPU数、内存大小等
shared_info: Arc<RwLock<InstanceInfo>>, // API Server线程与fc_vmm线程共享对象
// Guest VM core resources.
guest_memory: Option<GuestMemory>, // 虚拟机内存对象
kernel_config: Option<KernelConfig>, // 内核配置,如启动命令行参数
vcpus_handles: Vec<thread::JoinHandle<()>>, // vCPU线程返回句柄数组
exit_evt: Option<EpollEvent<EventFd>>, // 虚拟机退出事件eventfd
vm: Vm, // 虚拟机对象
// Guest VM devices.
mmio_device_manager: Option<MMIODeviceManager>, // mmio总线管理器
legacy_device_manager: LegacyDeviceManager, // legacy总线管理器
// Device configurations.
block_device_configs: BlockDeviceConfigs, // 后端存储块设备配置,供virtio-blk使用
network_interface_configs: NetworkInterfaceConfigs, // 后端网络接口配置,供virtio-net使用
…
epoll_context: EpollContext, // epoll事件循环上下文
// API resources.
api_event: EpollEvent<EventFd>, // 接收API Server线程通知的eventfd
from_api: Receiver<Box<VmmAction>>, // channel接收方,可接收来向API Server线程对象
…
}
impl Vmm {
fn new(
api_shared_info: Arc<RwLock<InstanceInfo>>,
api_event_fd: EventFd,
from_api: Receiver<Box<VmmAction>>,
seccomp_level: u32,
) -> Result<Self> {
let mut epoll_context = EpollContext::new()?; // 初始化epoll上下文
let api_event = epoll_context
.add_event(api_event_fd, EpollDispatch::VmmActionRequest) // 将api_event_fd添加到epoll上下,可监听该句柄
.expect("Cannot add API eventfd to epoll.");
let block_device_configs = BlockDeviceConfigs::new(); // 初始化存储配置对象
let kvm = KvmContext::new()?; // 创建KVM上下文
let vm = Vm::new(kvm.fd()).map_err(Error::Vm)?; // 创建虚拟机对象
Ok(Vmm {
kvm,
vm_config: VmConfig::default(),
shared_info: api_shared_info,
guest_memory: None,
kernel_config: None,
vcpus_handles: vec![],
exit_evt: None,
vm,
mmio_device_manager: None,
legacy_device_manager: LegacyDeviceManager::new().map_err(Error::CreateLegacyDevice)?,
block_device_configs,
network_interface_configs: NetworkInterfaceConfigs::new(),
…
epoll_context,
api_event,
from_api,
…
})
}
fn run_control(&mut self) -> Result<()> { // 事件循环框架
const EPOLL_EVENTS_LEN: usize = 100;
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; // 创建一个events数组,用于接收事件
let epoll_raw_fd = self.epoll_context.epoll_raw_fd;
'poll: loop { // 循环入口
let num_events = epoll::wait(epoll_raw_fd, -1, &mut events[..]).map_err(Error::Poll)?; // 通过epoll获知已经发生的事件
for event in events.iter().take(num_events) { // 针对已经发生的事件依次进行处理
let dispatch_idx = event.data as usize; // 获知事件dispatch_idx,注册事件时传入
let evset = match epoll::Events::from_bits(event.events) { // 获知具体事件,如POLLIN
…
};
if let Some(dispatch_type) = self.epoll_context.dispatch_table[dispatch_idx] { // 根据disptach_idx找到事件类型,注册时填入
match dispatch_type {
EpollDispatch::Exit => { // 第一类别,退出;VCPU线程退出时产生
…
}
EpollDispatch::Stdin => { // 第二类型,标准输入;使能串口使注册,接
// 收标准输入并作为虚拟机串口的输入
…
}
EpollDispatch::DeviceHandler(device_idx, device_token) => { // 第三类型,virtio设备IO处理;VCPU通过io_event_fd产生
match self
.epoll_context
.get_device_handler_by_handler_id(device_idx) // 首次处理时通过channel获取EpollHandler对象,回顾virtio
{ // 设备的activate流程
Ok(handler) => match handler.handle_event(device_token, evset) { // 调用EpollHandler对象的handle_event函数
…
},
…
}
}
EpollDispatch::VmmActionRequest => { // 第四类型,管理动作;来作API Server线程
self.api_event.fd.read().map_err(Error::EventFd)?;
self.run_vmm_action().unwrap_or_else(|_| { // 调用run_vmm_action
…
});
}
…
}
}
}
}
}
firecracker启动初期,fc_vmm线程仅对api_event_fd进行监听,即仅能对第四类型事件进行处理。回顾首篇对firecracker使用流程的介绍,我们通过curl工具对firecracker进行kernel、rootfs和虚拟机的配置后,最后通过InstanceStart命令启动虚拟机。这里的配置和启动命令最后都交由fc_vmm的run_vmm_action函数进行处理:
fn run_vmm_action(&mut self) -> Result<()> {
let request = match self.from_api.try_recv() { // 从API Server线程接收用户请求
…
};
match request { // 根据用户请求类别分别进行处理
VmmAction::ConfigureBootSource(boot_source_body, sender) => { // 内核启动参数配置
Vmm::send_response(
self.configure_boot_source(
boot_source_body.kernel_image_path,
boot_source_body.boot_args,
),
sender,
);
}
…
VmmAction::InsertBlockDevice(block_device_config, sender) => { // 配置存储块设备
Vmm::send_response(self.insert_block_device(block_device_config), sender);
}
VmmAction::InsertNetworkDevice(netif_body, sender) => { // 配置网络接口设备
Vmm::send_response(self.insert_net_device(netif_body), sender);
}
…
VmmAction::StartMicroVm(sender) => { // 配置完成后,启动一个虚拟机
Vmm::send_response(self.start_microvm(), sender);
}
VmmAction::SetVmConfiguration(machine_config_body, sender) => { // 配置虚拟机CPU和内存等
Vmm::send_response(self.set_vm_configuration(machine_config_body), sender);
}
…
};
Ok(())
}
最后,我们来看一下firecracker虚拟机的启动过程,对各个子系统初始化进行一些串联:
fn start_microvm(&mut self) -> std::result::Result<VmmData, VmmActionError> {
…
self.shared_info
.write() // 对共享对象加写锁
.expect("Failed to start microVM because shared info couldn't be written due to poisoned lock")
.state = InstanceState::Starting; // 将虚拟机状态设为Starting,完成后自动解锁(Rust语言特性)
self.init_guest_memory()?; // 虚拟内存初始化,参考CPU与内存部分
let vcpus;
#[cfg(target_arch = "x86_64")]
{
self.setup_interrupt_controller()?; // 中断控制器初始化,参考时钟与中断部分
self.attach_virtio_devices()?; // 添加virito-blk/net,内部将调用register_virtio_device,参考virtio设备部分
self.attach_legacy_devices()?; // legacy设备初始化,参考legacy设备部分
let entry_addr = self.load_kernel()?; // 加载ELF内核到entry_addr
vcpus = self.create_vcpus(entry_addr, request_ts)?; // 创建VCPU,参考CPU与内存部分
}
…
self.configure_system()?; // 配置系统,主要是生成mptable和引导数据头部
self.register_events()?; // 向epoll事件循环注册退出事件和标准输入事件
self.start_vcpus(vcpus)?; // 启动虚拟CPU,参考CPU与内存部分
self.shared_info
.write() // 重新对共享对象加写锁
.expect("Failed to start microVM because shared info couldn't be written due to poisoned lock")
.state = InstanceState::Running; // 将虚拟机状态设为Running,代表虚拟机已正常运行!!!
…
Ok(VmmData::Empty)
}
转载请注明:吴斌的博客 » 【firecracker】系统启动与epoll事件循环