正如上一节所说,Qemu中对不同类型的CPU做了具体的模拟。而且根据不同的CPU类型和Machine类型其初始化过程又有些不同。
这里我们就来看看X86_CPU。
继承关系
TYPE_DEVICE
+-------------------------------+
|class_size | = sizeof(DeviceClass)
|class_init | = device_class_init
| |
|instance_size | = sizeof(Object)
|instance_init | = device_initfn
|instance_finalize | = device_finalize
| |
|realize | = x86_cpu_realizefn
+-------------------------------+
TYPE_CPU
+-------------------------------+
|class_size | = sizeof(CPUClass)
|class_init | = cpu_class_init
| |
|instance_size | = sizeof(CPUState)
|instance_init | = cpu_common_initfn
|instance_finalize | = cpu_common_finalize
+-------------------------------+
TYPE_X86_CPU
+-------------------------------+
|abstract | = true
|class_size | = sizeof(X86CPUClass)
|class_init | = x86_cpu_common_class_init
| |
|instance_size | = sizeof(X86CPU)
|instance_init | = x86_cpu_initfn
| |
|parent_realize | = cpu_common_realizefn
+-------------------------------+
base-x86_64-cpu qemu64-x86_64-cpu host-x86_64-cpu
+-------------------------------+ +-------------------------------+ +-------------------------------+
|class_size | |class_size | |class_size |
|class_init | = x86_cpu_base_class_init |class_init | = x86_cpu_cpudef_class_init |class_init | = host_x86_cpu_class_init
| | | | | |
|instance_size | |instance_size | |instance_size |
|instance_init | |instance_init | |instance_init |
+-------------------------------+ +-------------------------------+ +-------------------------------+
这个继承关系略有点让人烦
貌似现在默认使用的是qemu64-x86_64-cpu
类型定义
首先我们要来说说CPU类型定义,不仅使用了常见的方式,还用了一个函数来注册CPU类型。
static void x86_cpu_register_types(void)
{
int i;
type_register_static(&x86_cpu_type_info);
for (i = 0; i < ARRAY_SIZE(builtin_x86_defs); i++) {
x86_register_cpudef_type(&builtin_x86_defs[i]);
}
type_register_static(&max_x86_cpu_type_info);
type_register_static(&x86_base_cpu_type_info);
#if defined(CONFIG_KVM) || defined(CONFIG_HVF)
type_register_static(&host_x86_cpu_type_info);
#endif
}
是不是比之前的有点丑?丑的还没有展开呢。
x86_register_cpudef_type()会对builtin_x86_defs数组一次进行注册,里面标明了具体cpu的型号和对应能支持的功能。那里面简直就是。。。当然看着看着也就习惯了。
指定CPU类型
既然定义了这么多的CPU类型,那就可以在启动qemu的时候指定才对。方法如下:
这里的MODEL就是你可以指定的类型了。具体qemu支持哪些类型,可以通过命令qemu -cpu help来查看。
故事讲到这里就结束本来也是可以的,不过我打算再深入看一下代码中是哪里解析命令行的CPU类型并且找到我们注册的这些类型的。
说起来也不难,就是在main函数中调用了parse_cpu_option这个函数。
parse_cpu_optioin(), parse option "cpu"
model_pieces = g_strsplit(cpu_option, ",", 2)
oc = cpu_class_by_name(CPU_RESOLVING_TYPE, model_pieces)
cpu_type = object_class_get_name(oc);
cc->class_by_name(cpu_model) = x86_cpu_class_by_name
cc->parse_features(cpu_type, ) = x86_cpu_parse_featurestr
看明白了,套路就是这么简单。
最终得到的这个类型将被赋值到current_machine->cpu_type等待这后续初始化过程中被使用。
除此之外,还解析了是否有增加或者减少的feature。比如命令行中如下:
-cpu host,+movdir64b,-movdiri
这部分将在初始化cpu的时候用到,这里知道就行了。
初始化
从创建Machine开始
CPU和Machine看来还是非常紧密联系在一起的。这点我们从创建cpu的流程中可以看出。
在PCMachine上,cpu初始化的一个简单流程如下:
pc_init1/pc_q35_init
x86_cpus_init
x86_cpu_new()
object_new(MACHINE(x86ms)->cpu_type)
看到了熟悉的cpu_type没有?对了,这个就是之前解析cpu类型时得到的值。由此证明了命令行传入的类型会用来创建cpu。
X86 CPU的初始化细节
CPU的初始化涉及到很多内容,比如支持什么特性,创建vcpu线程,如何接收信号等。在这里我们只是宏观得展开,具体细节有待专门章节讲解。
object_new(MACHINE(x86ms)->cpu_type)
cpu_common_initfn
cpu_exec_initfn()
x86_cpu_initfn
x86_cpu_register_feature_bit_props()
x86_cpu_load_model(xcc->model)
x86_cpu_realizefn
x86_cpu_expand_features(), expand cpuid
plus_features, passed from qemu command line
minus_features, passed from qemu command line
x86_cpu_filter_features()
cpu_exec_realizefn()
cpu_list_add()
vmstate_register()
mce_init()
qemu_init_vcpu
qemu_kvm_start_vcpu->qemu_kvm_cpu_thread_fn
x86_cpu_apic_realize()
object_property_set_bool(OBJECT(cpu->apic_state), true, "realized",)
Map APIC MMIO area
xcc->parent_realize = cpu_common_realizefn
作为DEVICE类型的子类,初始化流程自然包含了两个方面:
仔细看图的朋友估计已经发现了,初始化过程中的第二点,也就是类型中的realize函数发生了微妙的变化。
Device类型的realize变成了 x86_cpu_realizefn
TYPE_X86_CPU的realize变成了 cpu_common_realizefn
这个偷梁换柱的工作是 x86_cpu_common_class_init 干的。
所以当一个cpu要实例化的时候,调用的realize函数会走到x86_cpu_realizefn,而不是通常的cpu_common_realizefn。
在整个realize过程中,主要完成了几件事:
整理了feature,按照当前的了解就是哪些cpuid是支持的
因为每个cpu对应一个线程,每个cpu上也有自己的apic,所以每个vcpu都会对应去做这样的操作。