客户机管理工具
VM 列表管理
数据结构设计
VMList 结构(kernel/src/vmm/vm_list.rs):
/// VM 列表管理器
///
/// 使用 BTreeMap 而不是 HashMap 的原因:
/// 1. VM ID 通常是连续的小整数(0-255)
/// 2. BTreeMap 提供有序遍历(按 VM ID 排序)
/// 3. 小规模数据下性能相当,内存占用更小
/// 4. 支持范围查询(如获取 ID 0-10 的 VM)
struct VMList {
vm_list: BTreeMap<usize, VMRef>,
}
/// 全局 VM 列表(使用 Mutex 保护)
static GLOBAL_VM_LIST: Mutex<VMList> = Mutex::new(VMList::new());
BTreeMap vs HashMap 对比:
| 特性 | BTreeMap | HashMap |
|---|---|---|
| 查找时间 | O(log n) | O(1) 平均 |
| 插入时间 | O(log n) | O(1) 平均 |
| 删除时间 | O(log n) | O(1) 平均 |
| 遍历顺序 | 有序(按 key) | 无 序 |
| 内存占用 | 较小 | 较大(需要额外空间) |
| 范围查询 | 支持 | 不支持 |
| 适用场景 | 小规模有序数据 | 大规模无序数据 |
为什么使用 BTreeMap:
// 场景 1: 按顺序列出所有 VM
for (vm_id, vm) in vm_list.iter() {
println!("VM[{}]: {}", vm_id, vm.name());
// BTreeMap 保证按 ID 递增顺序输出
}
// 场景 2: 查找特定 ID 范围的 VM
let vms_0_to_10: Vec<_> = vm_list
.range(0..=10) // BTreeMap 支持范围查询
.collect();
// 场景 3: VM 数量通常很小(< 10)
// O(log n) vs O(1) 差异可忽略
VMList 实现
impl VMList {
/// 创建新的 VM 列表
const fn new() -> Self {
Self {
vm_list: BTreeMap::new(),
}
}
/// 添加 VM 到列表
///
/// # 参数
/// - vm_id: VM 唯一标识符
/// - vm: VM 引用(Arc)
///
/// # Panic
/// - 如果 VM ID 已存在
fn push_vm(&mut self, vm_id: usize, vm: VMRef) {
if self.vm_list.contains_key(&vm_id) {
panic!("VM with id {} already exists", vm_id);
}
self.vm_list.insert(vm_id, vm);
info!("VM[{}] added to global list", vm_id);
}
/// 根据 ID 获取 VM 引用
///
/// # 返回值
/// - Some(VMRef): VM 存在
/// - None: VM 不存在
fn get_vm_by_id(&self, vm_id: usize) -> Option<VMRef> {
self.vm_list.get(&vm_id).cloned()
}
/// 从列表中移除 VM
///
/// # 返回值
/// - Some(VMRef): VM 存在并已移除
/// - None: VM 不存在
fn remove_vm(&mut self, vm_id: usize) -> Option<VMRef> {
let vm = self.vm_list.remove(&vm_id);
if vm.is_some() {
info!("VM[{}] removed from global list", vm_id);
}
vm
}
}
全局 API
/// 添加 VM 到全局列表
pub fn push_vm(vm: VMRef) {
let vm_id = vm.id();
GLOBAL_VM_LIST.lock().push_vm(vm_id, vm);
}
/// 根据 ID 获取 VM
///
/// # 线程安全
/// 使用 Arc 引用计数,返回 VM 的克隆引用
/// 原始 VM 在列表中保留,引用计数 +1
pub fn get_vm_by_id(vm_id: usize) -> Option<VMRef> {
GLOBAL_VM_LIST.lock().get_vm_by_id(vm_id)
}
/// 从列表移除 VM
///
/// # 注意
/// - 仅从列表移除,不停止 VM
/// - 如果 VM 仍在运行,调用者需负责停止
pub fn remove_vm(vm_id: usize) -> Option<VMRef> {
GLOBAL_VM_LIST.lock().remove_vm(vm_id)
}
/// 获取所有 VM 的列表
///
/// # 返回值
/// Vec<VMRef>: 所有 VM 的引用(克隆)
///
/// # 性能
/// O(n),n 为 VM 数量
pub fn get_vm_list() -> Vec<VMRef> {
GLOBAL_VM_LIST
.lock()
.vm_list
.values()
.cloned()
.collect()
}
VCpu 任务管理
VMVCpus 数据结构
结构定义(kernel/src/vmm/vcpus.rs):
/// VM 的所有 VCpu 任务管理器
pub struct VMVCpus {
/// VM ID(仅用于调试)
_vm_id: usize,
/// 等待队列(用于 VCpu 同步)
wait_queue: WaitQueue,
/// VCpu 任务列表
/// 索引 = VCpu ID
vcpu_task_list: Vec<AxTaskRef>,
/// 运行中或暂停的 VCpu 计数
///
/// 用途:
/// - 跟踪有多少 VCpu 正在运行或处于 Halt 状态
/// - 当最后一个 VCpu 退出时,转换 VM 状态为 Stopped
///
/// 使用 AtomicUsize 原因:
/// - 多个 VCpu 线程并发访问
/// - 无需额外锁保护
/// - Relaxed 内存序即可(不需要同步其他数据)
running_halting_vcpu_count: AtomicUsize,
}
全局队列:
/// 全局 VCpu 任务队列
///
/// 为什么使用 UnsafeCell:
/// 1. VMVCpus 不实现 Sync,但我们需要全局访问
/// 2. 访问总是通过 VM ID 分区,避免数据竞争
/// 3. 手动保证线程安全(不同 VM 的 VCpu 不会冲突)
static VM_VCPU_TASK_WAIT_QUEUE: Queue = Queue::new();
struct Queue(UnsafeCell<BTreeMap<usize, VMVCpus>>);
unsafe impl Sync for Queue {}
unsafe impl Send for Queue {}
impl Queue {
const fn new() -> Self {
Self(UnsafeCell::new(BTreeMap::new()))
}
/// 获取不可变引用
///
/// # 安全性
/// 调用者需保证:
/// - 不会同时修改同一个 VM 的 VMVCpus
/// - 读取时没有并发写入
fn get(&self, vm_id: &usize) -> Option<&VMVCpus> {
unsafe { (*self.0.get()).get(vm_id) }
}
/// 获取可变引用
///
/// # 安全性
/// 调用者需保证:
/// - 独占访问(没有其他线程读取或写入)
#[allow(clippy::mut_from_ref)]
fn get_mut(&self, vm_id: &usize) -> Option<&mut VMVCpus> {
unsafe { (*self.0.get()).get_mut(vm_id) }
}
fn insert(&self, vm_id: usize, vcpus: VMVCpus) {
unsafe {
(*self.0.get()).insert(vm_id, vcpus);
}
}
fn remove(&self, vm_id: &usize) -> Option<VMVCpus> {
unsafe { (*self.0.get()).remove(vm_id) }
}
}
VCpu 任务生命周期
主 VCpu 设置(setup_vm_primary_vcpu):
/// 为 VM 设置主 vCPU(vCPU 0)
///
/// 调用时机:VM 初始化完成后,启动前
///
/// 流程:
/// 1. 创建 VMVCpus 管理器
/// 2. 创建主 VCpu 任务(处于阻塞状态)
/// 3. 添加到全局队列
pub fn setup_vm_primary_vcpu(vm: VMRef) {
info!("Initializing VM[{}]'s {} vcpus", vm.id(), vm.vcpu_num());
let vm_id = vm.id();
// 创建管理器
let mut vm_vcpus = VMVCpus::new(vm.clone());
// 创建主 vCPU(vCPU 0)
let primary_vcpu_id = 0;
let primary_vcpu = vm.vcpu_list()[primary_vcpu_id].clone();
let primary_vcpu_task = alloc_vcpu_task(vm.clone(), primary_vcpu);
// 添加任务
vm_vcpus.add_vcpu_task(primary_vcpu_task);
// 注册到全局队列
VM_VCPU_TASK_WAIT_QUEUE.insert(vm_id, vm_vcpus);
}
辅助 VCpu 启动(vcpu_on):
/// 启动辅助 vCPU(vCPU 1, 2, ...)
///
/// 调用时机:客户机执行 PSCI CPU_ON 时
/// 调用者:主 VCpu 或其他已运行的 VCpu
///
/// # 参数
/// - vm: VM 引用
/// - vcpu_id: 要启动的 VCpu ID
/// - entry_point: VCpu 入口地址(GPA)
/// - arg: 传递给 VCpu 的参数(寄存器 x0)
fn vcpu_on(
vm: VMRef,
vcpu_id: usize,
entry_point: GuestPhysAddr,
arg: usize,
) {
let vcpu = vm.vcpu_list()[vcpu_id].clone();
// 验证状态
assert_eq!(
vcpu.state(),
VCpuState::Free,
"vcpu_on: {} invalid vcpu state {:?}",
vcpu.id(),
vcpu.state()
);
// 设置入口和参数
vcpu.set_entry(entry_point)
.expect("vcpu_on: set_entry failed");
vcpu.set_gpr(0, arg); // x0 = arg
// 创建任务
let vcpu_task = alloc_vcpu_task(vm.clone(), vcpu);
// 添加到任务列表
VM_VCPU_TASK_WAIT_QUEUE
.get_mut(&vm.id())
.unwrap()
.add_vcpu_task(vcpu_task);
}
任务分配(alloc_vcpu_task):
/// 为 VCpu 分配 axtask 任务
///
/// 特点:
/// 1. 入口函数:vcpu_run(无限循环)
/// 2. 栈大小:256 KiB
/// 3. CPU 亲和性:根据配置绑定到特定物理 CPU
/// 4. 任务扩展:存储 VM 和 VCpu 引用
///
/// # 线程安全
/// - 使用 Weak 引用 VM,避免循环引用
/// - VCpu 引用使用 Arc,共享所有权
fn alloc_vcpu_task(vm: VMRef, vcpu: VCpuRef) -> AxTaskRef {
const KERNEL_STACK_SIZE: usize = 0x40000; // 256 KiB
info!("Spawning task for VM[{}] VCpu[{}]", vm.id(), vcpu.id());
// 创建任务
let mut vcpu_task = TaskInner::new(
vcpu_run, // 入口函数
format!("VM[{}]-VCpu[{}]", vm.id(), vcpu.id()),
KERNEL_STACK_SIZE,
);
// 设置 CPU 亲和性
if let Some(phys_cpu_set) = vcpu.phys_cpu_set() {
vcpu_task.set_cpumask(AxCpuMask::from_raw_bits(phys_cpu_set));
debug!(
"VCpu[{}] pinned to CPU mask: {:#b}",
vcpu.id(),
phys_cpu_set
);
}
// 关联 VM 和 VCpu
// 使用 Weak 引用避免 VM 无法释放
vcpu_task.init_task_ext(TaskExt::from_vm_ref(vm.clone(), vcpu));
info!(
"VCpu task {} created {:?}",
vcpu_task.id_name(),
vcpu_task.cpumask()
);
axtask::spawn_task(vcpu_task)
}
VCpu 主循环
vcpu_run 实现:
/// VCpu 任务主循环
///
/// 执行流程:
/// 1. 等待 VM 进入 Running 状态
/// 2. 循环运行 VCpu
/// 3. 处理 VMExit
/// 4. 检查 VM 状态(暂停/停止)
fn vcpu_run() {
let curr = axtask::current();
let vm = curr.task_ext().vm();
let vcpu = curr.task_ext().vcpu.clone();
let vm_id = vm.id();
let vcpu_id = vcpu.id();
// ═══════════════════════════════════════
// 阶段 1: 启动延迟
// ═══════════════════════════════════════
// 避免所有 VM 同时启动造成资源竞争
let boot_delay_sec = (vm_id - 1) * 5;
if boot_delay_sec > 0 {
info!("VM[{vm_id}] boot delay: {boot_delay_sec}s");
busy_wait(Duration::from_secs(boot_delay_sec as _));
}
// ═══════════════════════════════════════
// 阶段 2: 等待 VM 启动
// ═══════════════════════════════════════
info!("VM[{}] VCpu[{}] waiting for running", vm_id, vcpu_id);
wait_for(vm_id, || vm.running());
info!("VM[{}] VCpu[{}] running...", vm_id, vcpu_id);
mark_vcpu_running(vm_id);
// ═══════════════════════════════════════
// 阶段 3: 主循环
// ═══════════════════════════════════════
loop {
// 运行 VCpu
match vm.run_vcpu(vcpu_id) {
Ok(exit_reason) => {
// 处理 VMExit(详见 4.2.4)
handle_vmexit(exit_reason, &vm, &vcpu);
}
Err(err) => {
error!("VM[{vm_id}] VCpu[{vcpu_id}] error: {err:?}");
vm.shutdown().expect("VM shutdown failed");
}
}
// ═══════════════════════════════════════
// 检查 VM 暂停状态
// ═══════════════════════════════════════
if vm.suspending() {
debug!(
"VM[{}] VCpu[{}] suspended, waiting...",
vm_id, vcpu_id
);
wait_for(vm_id, || !vm.suspending());
info!("VM[{}] VCpu[{}] resumed", vm_id, vcpu_id);
continue;
}
// ═══════════════════════════════════════
// 检查 VM 停止状态
// ═══════════════════════════════════════
if vm.stopping() {
warn!(
"VM[{}] VCpu[{}] stopping",
vm_id, vcpu_id
);
// 最后一个退出的 VCpu 负责更新状态
if mark_vcpu_exiting(vm_id) {
info!("VM[{vm_id}] last VCpu exiting");
// 转换状态:Stopping -> Stopped
vm.set_vm_status(axvm::VMStatus::Stopped);
// 减少运行 VM 计数
sub_running_vm_count(1);
// 唤醒等待的线程
ax_wait_queue_wake(&super::VMM, 1);
}
break; // 退出主循环
}
}
info!("VM[{}] VCpu[{}] exiting...", vm_id, vcpu_id);
}
VMExit 处理
Hypercall:
AxVCpuExitReason::Hypercall { nr, args } => {
debug!("Hypercall [{nr}] args {args:x?}");
use crate::vmm::hvc::HyperCall;
match HyperCall::new(vcpu.clone(), vm.clone(), nr, args) {
Ok(hypercall) => {
let ret_val = match hypercall.execute() {
Ok(ret_val) => ret_val as isize,
Err(err) => {
warn!("Hypercall [{nr:#x}] failed: {err:?}");
-1
}
};
vcpu.set_return_value(ret_val as usize);
}
Err(err) => {
warn!("Hypercall [{nr:#x}] failed: {err:?}");
}
}
}
外部中断:
AxVCpuExitReason::ExternalInterrupt { vector } => {
debug!("VM[{vm_id}] VCpu[{vcpu_id}] IRQ {vector}");
// 分发中断到中断处理器
axhal::irq::irq_handler(vector as usize);
// 检查定时器事件
super::timer::check_events();
}
CPU 电源管理:
// CpuUp: 启动辅助 CPU
AxVCpuExitReason::CpuUp {
target_cpu,
entry_point,
arg,
} => {
info!(
"VM[{vm_id}] VCpu[{vcpu_id}] booting CPU {target_cpu} \
entry={entry_point:x} arg={arg:#x}"
);
// 从物理 CPU ID (MPIDR) 映射到 VCpu ID
let vcpu_mappings = vm.get_vcpu_affinities_pcpu_ids();
let target_vcpu_id = vcpu_mappings
.iter()
.find_map(|(vid, _, pid)| {
if *pid == target_cpu as usize {
Some(*vid)
} else {
None
}
})
.expect("Physical CPU ID not found");
// 启动目标 VCpu
vcpu_on(vm.clone(), target_vcpu_id, entry_point, arg);
vcpu.set_gpr(0, 0); // 返回成功
}
// CpuDown: 关闭 CPU
AxVCpuExitReason::CpuDown { _state } => {
warn!("VM[{vm_id}] VCpu[{vcpu_id}] CPU down");
wait(vm_id); // 进入等待状态
}
// Halt: 暂停执行(WFI 指令)
AxVCpuExitReason::Halt => {
debug!("VM[{vm_id}] VCpu[{vcpu_id}] Halt");
wait(vm_id);
}
核间中断(IPI):
AxVCpuExitReason::SendIPI {
target_cpu,
target_cpu_aux: _,
send_to_all,
send_to_self,
vector,
} => {
debug!(
"VM[{vm_id}] VCpu[{vcpu_id}] SendIPI \
target={target_cpu:#x} vector={vector}"
);
if send_to_all {
unimplemented!("Broadcast IPI not supported");
}
if target_cpu == vcpu_id as u64 || send_to_self {
// 发送给自己
inject_interrupt(vector as _);
} else {
// 发送给其他 VCpu
vm.inject_interrupt_to_vcpu(
CpuMask::one_shot(target_cpu as _),
vector as _,
)
.unwrap();
}
}
系统关机:
AxVCpuExitReason::SystemDown => {
warn!("VM[{vm_id}] VCpu[{vcpu_id}] system shutdown");
vm.shutdown().expect("VM shutdown failed");
}
AxVisor Shell 实现
AxVisor 提供了强大的交互式 Shell 命令系统,用于虚拟机的全生命周期管理。Shell 命令系统采用**命令树(Command Tree)架构,支持子命令、选项标志和位置参数的灵活组合。本节深入分析 Shell 命令的内部实现机制,包括命令解析、状态验证、批处理等核心功能。 AxVisor Shell 命令框架采用命令树(Command Tree)**架构,提供灵活、可扩展的命令管理系统。框架的核心设计理念是:
- 层次化组织: 使用树状结构组织命令,支持任意深度的子命令嵌套
- 声明式注册: 通过构建器模式声明式注册命令,代码简洁清晰
- 类型安全: 利用 Rust 类型系统确保参数解析的正确性
- 零成本抽象: 命令解析和分发在编译期完成大部分工作,运行时开销最小
设计目标
- 用户友好: 直观的命令语法(类似 Docker/Kubectl)
- 开发友好: 简单的命令添加流程,无需修改核心框架
- 类型安全: 编译期捕获大部分错误
- 性能优化: 最小化运行时解析开销
- 可扩展: 支持动态添加新命令模块
命令框架架构
整体架构
┌─────────────────────────────────────────────────────────────┐
│ User Input Layer │
│ (REPL / Script Execution) │
└────────────────────────┬ ────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Command Parser Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Tokenizer │─>│ Arg Parser │─>│ Validation │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└────────────────────────┬────────────────────────────────────┘
│ ParsedCommand
▼
┌─────────────────────────────────────────────────────────────┐
│ Command Tree Layer │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Root Command Node │ │
│ │ ├─ vm (CommandNode) │ │
│ │ │ ├─ start (handler: vm_start) │ │
│ │ │ ├─ stop (handler: vm_stop) │ │
│ │ │ └─ list (handler: vm_list) │ │
│ │ └─ help (CommandNode) │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────────┬────────────────────────────────────┘
│ Route to Handler
▼
┌─────────────────────────────────────────────────────────────┐
│ Command Handler Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ vm_start() │ │ vm_stop() │ │ vm_list() │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
└─────────┼──────────────────┼──────────────────┼─────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ VMM Core Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ vm_list │ │ vcpus │ │ config │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
命令解析流程
命令从用户输入到执行的完整流程:
// 完整的命令解析和执行流程示例
// 1. 用户输入
let input = "vm start 0 1 --detach";
// 2. 词法分析 (Tokenization)
let tokens = tokenize(input);
// => ["vm", "start", "0", "1", "--detach"]
// 3. 命令树查找
let command_tree = CommandTree::new();
let (node, remaining_tokens) = command_tree.find_command(&tokens);
// node: CommandNode for "vm start"
// remaining_tokens: ["0", "1", "--detach"]
// 4. 参数解析
let parsed = parse_arguments(node, remaining_tokens);
// => ParsedCommand {
// positional_args: ["0", "1"],
// flags: {"detach": true},
// options: {}
// }
// 5. 参数验证
validate_arguments(node, &parsed)?;
// 6. 执行处理函数
if let Some(handler) = node.handler {
handler(&parsed);
}
详细的解析阶段
阶段 1: 词法分析 (Tokenization)
将输入字符串分割成 token 列表,支持:
- 空白符分隔:
vm start 0→["vm", "start", "0"] - 引号包围:
vm create "my vm"→["vm", "create", "my vm"] - 转义字符:
echo \"hello\"→["echo", "\"hello\""]
/// 词法分析器:将输入字符串分割为 token 列表
///
/// # 支持的语法
/// - 空白符分隔: `vm start 0` -> ["vm", "start", "0"]
/// - 单引号: `echo 'hello world'` -> ["echo", "hello world"]
/// - 双引号: `echo "hello world"` -> ["echo", "hello world"]
/// - 转义字符: `echo \"test\"` -> ["echo", "\"test\""]
///
/// # 示例
/// ```
/// let input = "vm start 0 1 --name \"test vm\"";
/// let tokens = tokenize(input);
/// assert_eq!(tokens, vec!["vm", "start", "0", "1", "--name", "test vm"]);
/// ```
fn tokenize(input: &str) -> Vec<String> {
let mut tokens = Vec::new();
let mut current_token = String::new();
let mut in_quotes = false;
let mut quote_char = '\0';
let mut escaped = false;
for ch in input.chars() {
if escaped {
current_token.push(ch);
escaped = false;
continue;
}
match ch {
'\\' => {
escaped = true;
}
'"' | '\'' => {
if in_quotes {
if ch == quote_char {
// 结束引号
in_quotes = false;
quote_char = '\0';
} else {
current_token.push(ch);
}
} else {
// 开始引号
in_quotes = true;
quote_char = ch;
}
}
' ' | '\t' | '\n' => {
if in_quotes {
current_token.push(ch);
} else if !current_token.is_empty() {
tokens.push(current_token.clone());
current_token.clear();
}
}
_ => {
current_token.push(ch);
}
}
}
if !current_token.is_empty() {
tokens.push(current_token);
}
tokens
}
阶段 2: 命令查找 (Command Lookup)
在命令树中查找匹配的命令节点:
/// 在命令树中查找命令节点
///
/// # 查找过程
/// 1. 从根节点开始
/// 2. 逐层匹配 token
/// 3. 遇到叶子节点或无法匹配时停止
/// 4. 返回最后匹配的节点和剩余 token
///
/// # 示例
/// ```
/// let tokens = vec!["vm", "start", "0", "1"];
/// let (node, remaining) = tree.find_command(&tokens);
/// // node: CommandNode for "vm start"
/// // remaining: ["0", "1"]
/// ```
impl CommandTree {
pub fn find_command<'a>(
&self,
tokens: &'a [String],
) -> Result<(&CommandNode, &'a [String]), CommandError> {
if tokens.is_empty() {
return Err(CommandError::EmptyCommand);
}
let mut current_node = &self.root;
let mut depth = 0;
// 逐层匹配命令路径
for (i, token) in tokens.iter().enumerate() {
// 尝试在当前节点的子命令中查找
if let Some(subcommand) = current_node.subcommands.get(token) {
current_node = subcommand;
depth = i + 1;
} else {
// 找不到子命令,停止查找
break;
}
}
// 检查是否找到有效的处理函数
if current_node.handler.is_none() && depth < tokens.len() {
return Err(CommandError::UnknownCommand(tokens[depth].clone()));
}
Ok((current_node, &tokens[depth..]))
}
}
阶段 3: 参数解析 (Argument Parsing)
将剩余的 token 解析为位置参数、选项和标志:
/// 参数解析器:将 token 列表解析为结构化的命令参数
///
/// # 参数类型
/// - 位置参数: `vm start 0 1` -> positional_args: ["0", "1"]
/// - 长选项: `--format json` -> options: {"format": "json"}
/// - 长选项(等号): `--format=json` -> options: {"format": "json"}
/// - 短选项: `-f json` -> options: {"f": "json"}
/// - 标志: `--force` -> flags: {"force": true}
///
/// # 解析规则
/// 1. 以 `--` 开头的是长选项或长标志
/// 2. 以 `-` 开头的是短选项或短标志
/// 3. 不以 `-` 开头的是位置参数
/// 4. 如果选项后面没有值,视为标志
fn parse_arguments(
node: &CommandNode,
tokens: &[String],
) -> Result<ParsedCommand, ParseError> {
let mut positional_args = Vec::new();
let mut options = BTreeMap::new();
let mut flags = BTreeMap::new();
let mut i = 0;
while i < tokens.len() {
let token = &tokens[i];
if token.starts_with("--") {
// 长选项或长标志
let key_value = token.trim_start_matches("--");
if let Some(eq_pos) = key_value.find('=') {
// --option=value 格式
let key = &key_value[..eq_pos];
let value = &key_value[eq_pos + 1..];
options.insert(key.to_string(), value.to_string());
} else {
// 检查下一个 token 是否是值
if i + 1 < tokens.len() && !tokens[i + 1].starts_with('-') {
// --option value 格式
options.insert(key_value.to_string(), tokens[i + 1].clone());
i += 1; // 跳过下一个 token
} else {
// --flag 格式
flags.insert(key_value.to_string(), true);
}
}
} else if token.starts_with('-') && token.len() > 1 {
// 短选项或短标志
let key = token.trim_start_matches('-');
if i + 1 < tokens.len() && !tokens[i + 1].starts_with('-') {
// -o value 格式
options.insert(key.to_string(), tokens[i + 1].clone());
i += 1;
} else {
// -f 格式(标志)
flags.insert(key.to_string(), true);
}
} else {
// 位置参数
positional_args.push(token.clone());
}
i += 1;
}
Ok(ParsedCommand {
positional_args,
options,
flags,
})
}
阶段 4: 参数验证 (Validation)
验证解析后的参数是否符合命令的要求:
/// 参数验证:检查解析后的参数是否符合命令定义
///
/// # 验证项
/// 1. 必需选项是否都已提供
/// 2. 选项值的类型是否正确
/// 3. 未知选项/标志检测
/// 4. 位置参数数量检查
fn validate_arguments(
node: &CommandNode,
parsed: &ParsedCommand,
) -> Result<(), ParseError> {
// 检查必需的选项
for opt_def in &node.options {
if opt_def.required && !parsed.options.contains_key(&opt_def.name) {
return Err(ParseError::MissingRequiredOption(opt_def.name.clone()));
}
}
// 检查未知选项
for key in parsed.options.keys() {
let is_valid = node.options.iter().any(|opt| {
opt.name == *key || opt.short_name.as_ref() == Some(key)
});
if !is_valid {
return Err(ParseError::UnknownOption(key.clone()));
}
}
// 检查未知标志
for key in parsed.flags.keys() {
let is_valid = node.flags.iter().any(|flag| {
flag.name == *key || flag.short_name.as_ref() == Some(key)
});
if !is_valid {
return Err(ParseError::UnknownFlag(key.clone()));
}
}
Ok(())
}
命令树设计
命令树是框架的核心数据结构,使用 BTreeMap 组织子命令,提供有序遍历能力。
设计理念
理念 1: 层次化命名空间
- 不同功能域的命令分属不同子树
- 避免命名冲突,易于管理
理念 2: 递归结构
- 每个节点可以是命令(有 handler)
- 每个节点也可以是命令组(有 subcommands)
- 支持任意深度的嵌套
理念 3: 延迟执行
- 命令树只负责路由,不执行业务逻辑
- 处理函数通过函数指针注册,保持低耦合
理念 4: 自动生成帮助
- 每个节点包含描述和使用说明
- 可递归展示整个命令树结构
树的组织示例
Root CommandTree
│
├─── vm (CommandNode)
│ ├─── handler: None // 命令组,无处理函数
│ ├─── description: "Virtual machine management"
│ ├─── subcommands:
│ │ ├─── start (CommandNode)
│ │ │ ├─── handler: Some(vm_start)
│ │ │ ├─── description: "Start virtual machines"
│ │ │ ├─── flags: [FlagDef { name: "detach", ... }]
│ │ │ └─── options: []
│ │ │
│ │ ├─── stop (CommandNode)
│ │ │ ├─── handler: Some(vm_stop)
│ │ │ ├─── flags: [FlagDef { name: "force", ... }]
│ │ │ └─── options: []
│ │ │
│ │ └─── list (CommandNode)
│ │ ├─── handler: Some(vm_list)
│ │ ├─── options: [OptionDef { name: "format", ... }]
│ │ └─── flags: []
│ │
├─── vcpu (CommandNode)
│ ├─── handler: None
│ └─── subcommands: [...]
│
└─── help (CommandNode)
└─── handler: Some(show_help)
参数解析机制
参数分为三类:位置参数、选项和标志。
参数类型对比
| 参数类型 | 语法示例 | 解析结果 | 用途 |
|---|---|---|---|
| 位置参数 | vm start 0 1 | positional_args: ["0", "1"] | 必需的主要参数(VM ID 等) |
| 选项(长) | --format json | options: {"format": "json"} | 可选配置,需要值 |
| 选项(短) | -f json | options: {"f": "json"} | 选项的简写形式 |
| 选项(等号) | --format=json | options: {"format": "json"} | 选项的紧凑形式 |
| 标志(长) | --force | flags: {"force": true} | 布尔开关,无需值 |
| 标志(短) | -f | flags: {"f": true} | 标志的简写形式 |
解析优先级和规则
// 解析规则决策树
if token.starts_with("--") {
// 1. 长选项或长标志
let key = token.trim_start_matches("--");
if key.contains('=') {
// 1a. --option=value 格式
parse_as_option_with_equals();
} else if next_token_is_value() {
// 1b. --option value 格式
parse_as_option_with_space();
} else {
// 1c. --flag 格式
parse_as_flag();
}
} else if token.starts_with('-') && token.len() > 1 {
// 2. 短选项或短标志
let key = token.trim_start_matches('-');
if next_token_is_value() {
// 2a. -o value 格式
parse_as_short_option();
} else {
// 2b. -f 格式
parse_as_short_flag();
}
} else {
// 3. 位置参数
parse_as_positional_arg();
}
特殊语法支持
1. 混合参数顺序
# 灵活的参数顺序
vm start --force 0 1 --detach
vm start 0 1 --force --detach
vm start 0 --force 1 --detach
# 都解析为:
# positional_args: ["0", "1"]
# flags: {"force": true, "detach": true}
2. 多值选项(可选扩展)
# 未来支持多值选项
vm create --cpu 0,1,2 --cpu 3,4
# options: {"cpu": ["0,1,2", "3,4"]}
3. 参数终止符 --(可选扩展)
# -- 之后的所有内容都视为位置参数
vm exec 0 -- ls -la --color=auto
# positional_args: ["ls", "-la", "--color=auto"]
核心数据结构
CommandNode - 命令节点
/// 命令树的节点
///
/// 每个节点可以是:
/// 1. 命令组(handler = None, subcommands 非空)
/// 2. 叶子命令(handler = Some, subcommands 可空)
/// 3. 混合节点(handler = Some, subcommands 非空)
#[derive(Debug, Clone)]
pub struct CommandNode {
/// 命令处理函数
/// None 表示这是一个命令组,需要继续匹配子命令
pub handler: Option<fn(&ParsedCommand)>,
/// 子命令映射
/// Key: 子命令名称
/// Value: 子命令节点
pub subcommands: BTreeMap<String, CommandNode>,
/// 命令的简短描述
/// 用于生成帮助信息
pub description: &'static str,
/// 命令的使用说明
/// 格式: "command [OPTIONS] <ARGS>"
pub usage: Option<&'static str>,
/// 支持的选项定义列表
pub options: Vec<OptionDef>,
/// 支持的标志定义列表
pub flags: Vec<FlagDef>,
}
impl CommandNode {
/// 创建新的命令节点
pub const fn new(description: &'static str) -> Self {
Self {
handler: None,
subcommands: BTreeMap::new(),
description,
usage: None,
options: Vec::new(),
flags: Vec::new(),
}
}
/// 构建器模式:设置处理函数
pub fn with_handler(mut self, handler: fn(&ParsedCommand)) -> Self {
self.handler = Some(handler);
self
}
/// 构建器模式:设置使用说明
pub fn with_usage(mut self, usage: &'static str) -> Self {
self.usage = Some(usage);
self
}
/// 构建器模式:添加子命令
pub fn add_subcommand(
mut self,
name: &str,
subcommand: CommandNode,
) -> Self {
self.subcommands.insert(name.to_string(), subcommand);
self
}
/// 构建器模式:添加选项
pub fn add_option(mut self, option: OptionDef) -> Self {
self.options.push(option);
self
}
/// 构建器模式:添加标志
pub fn add_flag(mut self, flag: FlagDef) -> Self {
self.flags.push(flag);
self
}
}
OptionDef - 选项定义
/// 命令选项定义
///
/// 选项是需要值的参数,如 `--format json`
#[derive(Debug, Clone)]
pub struct OptionDef {
/// 选项名称(长格式)
/// 例如: "format"
pub name: String,
/// 选项短名称(可选)
/// 例如: Some("f")
pub short_name: Option<String>,
/// 选项描述
pub description: &'static str,
/// 是否必需
pub required: bool,
/// 默认值(可选)
pub default_value: Option<String>,
/// 值的类型提示(用于帮助信息)
/// 例如: "FORMAT", "PATH", "NUMBER"
pub value_type: &'static str,
}
impl OptionDef {
/// 创建新的选项定义
pub fn new(name: &str, description: &'static str) -> Self {
Self {
name: name.to_string(),
short_name: None,
description,
required: false,
default_value: None,
value_type: "VALUE",
}
}
/// 设置短名称
pub fn with_short(mut self, short: &str) -> Self {
self.short_name = Some(short.to_string());
self
}
/// 设置为必需选项
pub fn required(mut self) -> Self {
self.required = true;
self
}
/// 设置默认值
pub fn with_default(mut self, value: &str) -> Self {
self.default_value = Some(value.to_string());
self
}
/// 设置值类型提示
pub fn with_value_type(mut self, value_type: &'static str) -> Self {
self.value_type = value_type;
self
}
}
FlagDef - 标志定义
/// 命令标志定义
///
/// 标志是布尔开关,不需要值,如 `--force`
#[derive(Debug, Clone)]
pub struct FlagDef {
/// 标志名称(长格式)
/// 例如: "force"
pub name: String,
/// 标志短名称(可选)
/// 例如: Some("f")
pub short_name: Option<String>,
/// 标志描述
pub description: &'static str,
}
impl FlagDef {
/// 创建新的标志定义
pub fn new(name: &str, description: &'static str) -> Self {
Self {
name: name.to_string(),
short_name: None,
description,
}
}
/// 设置短名称
pub fn with_short(mut self, short: &str) -> Self {
self.short_name = Some(short.to_string());
self
}
}
ParsedCommand - 解析结果
/// 解析后的命令
///
/// 包含所有解析出的参数信息
#[derive(Debug)]
pub struct ParsedCommand {
/// 位置参数列表
/// 例如: ["0", "1", "2"]
pub positional_args: Vec<String>,
/// 选项映射
/// Key: 选项名称
/// Value: 选项值
/// 例如: {"format": "json", "output": "/tmp/out.txt"}
pub options: BTreeMap<String, String>,
/// 标志映射
/// Key: 标志名称
/// Value: 是否设置(始终为 true)
/// 例如: {"force": true, "verbose": true}
pub flags: BTreeMap<String, bool>,
}
impl ParsedCommand {
/// 获取位置参数(按索引)
pub fn get_arg(&self, index: usize) -> Option<&String> {
self.positional_args.get(index)
}
/// 获取选项值
pub fn get_option(&self, name: &str) -> Option<&String> {
self.options.get(name)
}
/// 获取标志状态
pub fn has_flag(&self, name: &str) -> bool {
self.flags.get(name).copied().unwrap_or(false)
}
/// 位置参数数量
pub fn arg_count(&self) -> usize {
self.positional_args.len()
}
}
CommandTree - 命令树
/// 命令树
///
/// 管理所有注册的命令
pub struct CommandTree {
/// 根节点
root: CommandNode,
}
impl CommandTree {
/// 创建新的命令树
pub fn new() -> Self {
let mut root = CommandNode::new("AxVisor Shell");
// 注册内置命令
root = root
.add_subcommand("help", build_help_command())
.add_subcommand("exit", build_exit_command());
// 注册 VM 命令
root = root.add_subcommand("vm", build_vm_command());
// 未来可以添加更多命令模块
// root = root.add_subcommand("vcpu", build_vcpu_command());
// root = root.add_subcommand("config", build_config_command());
Self { root }
}
/// 执行命令
pub fn execute(&self, input: &str) {
// 1. 词法分析
let tokens = tokenize(input);
if tokens.is_empty() {
return;
}
// 2. 命令查找
let (node, remaining) = match self.find_command(&tokens) {
Ok(result) => result,
Err(e) => {
eprintln!("Error: {:?}", e);
return;
}
};
// 3. 参数解析
let parsed = match parse_arguments(node, remaining) {
Ok(cmd) => cmd,
Err(e) => {
eprintln!("Error: {:?}", e);
self.show_usage(node);
return;
}
};
// 4. 参数验证
if let Err(e) = validate_arguments(node, &parsed) {
eprintln!("Error: {:?}", e);
self.show_usage(node);
return;
}
// 5. 执行处理函数
if let Some(handler) = node.handler {
handler(&parsed);
} else {
eprintln!("Error: Command has no handler");
self.show_usage(node);
}
}
/// 显示命令用法
fn show_usage(&self, node: &CommandNode) {
if let Some(usage) = node.usage {
println!("Usage: {}", usage);
}
println!("{}", node.description);
if !node.options.is_empty() {
println!("\nOptions:");
for opt in &node.options {
print!(" --{}", opt.name);
if let Some(short) = &opt.short_name {
print!(", -{}", short);
}
println!(" <{}> {}", opt.value_type, opt.description);
}
}
if !node.flags.is_empty() {
println!("\nFlags:");
for flag in &node.flags {
print!(" --{}", flag.name);
if let Some(short) = &flag.short_name {
print!(", -{}", short);
}
println!(" {}", flag.description);
}
}
}
}
命令注册机制
命令注册采用构建器模式,代码简洁易读。