vcpu的初始化函數(shù)注冊
在accel/kvm/kvm_all
中的最后一行
type_init(kvm_type_init)
kvm_type_init
type_register_static(&kvm_accel_type)
kvm_accel_type.class_init=kvm_accel_class_init
kvm_accel_class_init
中設(shè)置ac->init_machine=kvm_init
在kvm_init
中執(zhí)行初始化熄求。
其中灸眼,type_init(kvm_type_init)
只是把kvm_type_init插入給了init_type_list[MODULE_INIT_QOM]
鏈表中马胧,并把type_register_static(&kvm_accel)作為該成員的e->init
而已。
而這個(gè)初始化函數(shù),則是在module_call_init(MODULE_INIT_QOM)
中調(diào)用旗国,此時(shí)會把這個(gè)type_info
褐望,即kvm_accel_type
得相關(guān)信息,生成一個(gè)TypeImpl
得類型挣轨,插入到全局類型哈希表type_table
中军熏。
kvm_init初始化函數(shù)的執(zhí)行
而我們注冊的ac->init_machine
何時(shí)被調(diào)用呢?
他就是在vl.c
的main函數(shù)中卷扮,在configure_accelerator()
函數(shù)中被調(diào)用荡澎,首先,在尋找加速器accel
的過程中晤锹,會根據(jù)傳入的參數(shù)摩幔,調(diào)用accel_find()
函數(shù),在全局的type_table
中找到相應(yīng)的KVM類鞭铆,然后把它轉(zhuǎn)換成AccelClass
返回到configure_accelerator
函數(shù)中热鞍,緊接著調(diào)用accel_init_machine
函數(shù),在這個(gè)函數(shù)中衔彻,則會調(diào)用acc->init_machine(ms)
函數(shù)薇宠,由于之前注冊了ac->init_machine=kvm_init
,所以此時(shí)實(shí)際調(diào)用的就是kvm_init
函數(shù)艰额,從而完成KVM的初始化澄港。
vpu的初始化以及每個(gè)vcpu主線程的執(zhí)行流程
在target/i386/cpu.c
中設(shè)置了x86_cpu_type_info.class_init=x86_cpu_common_class_init
,并調(diào)用x86_cpu_register_types
將其在初始化時(shí)注冊進(jìn)MODULE_INIT_QOM
類型中
x86_cpu_common_class_init
中調(diào)用device_class_set_parent_realize(dc, x86_cpu_realizefn, &xcc->parent_realize)
x86_cpu_realizefn()
調(diào)用
qemu_init_vpcu
(在cpus.c
中)調(diào)用
qemu_kvm_start_vcpu
柄沮,在這里面回梧,調(diào)用了qemu_thread_create(cpu->thread, thread_name, qemu_kvm_cpu_thread_fn, cpu, QEMU_THREAD_JOINABLE)
增加了qemu_kvm_cpu_thread_fn
作為線程工作函數(shù):
static void *qemu_kvm_cpu_thread_fn(void *arg)
{
CPUState *cpu = arg;
int r;
rcu_register_thread();
qemu_mutex_lock_iothread();
qemu_thread_get_self(cpu->thread);
cpu->thread_id = qemu_get_thread_id();
cpu->can_do_io = 1;
current_cpu = cpu;
r = kvm_init_vcpu(cpu);
if (r < 0) {
error_report("kvm_init_vcpu failed: %s", strerror(-r));
exit(1);
}
kvm_init_cpu_signals(cpu);
/* signal CPU creation */
cpu->created = true;
qemu_cond_signal(&qemu_cpu_cond);
do {
if (cpu_can_run(cpu)) {
r = kvm_cpu_exec(cpu);
if (r == EXCP_DEBUG) {
cpu_handle_guest_debug(cpu);
}
}
qemu_wait_io_event(cpu);
} while (!cpu->unplug || cpu_can_run(cpu));
qemu_kvm_destroy_vcpu(cpu);
cpu->created = false;
qemu_cond_signal(&qemu_cpu_cond);
qemu_mutex_unlock_iothread();
rcu_unregister_thread();
return NULL;
}
在這里,調(diào)用了kvm_init_vcpu(cpu)
進(jìn)行初始化祖搓,其實(shí)主要也就是用ioctl
創(chuàng)建vpu狱意,并給vcpu進(jìn)行一些屬性的初始化。
內(nèi)存初始化的執(zhí)行
在hw/i386/pc_piix.c
中的pc_init1
函數(shù)中拯欧,會首先初始化ram_size
首先對max_ram_below_4g
這個(gè)元素進(jìn)行了初始化详囤,默認(rèn)初始化成了0xe0000000
,即3.5G的默認(rèn)值,并且是初始化了above_4g_mem_size
藏姐。
在pc_memory_init
中隆箩,則是調(diào)用了memory_region_allocate_system_memory
(位于numa.c:510
),此處則是調(diào)用了allocate_system_memory_nonnuma
, 在這其中則調(diào)用了memory_region_init_ram_from_file
函數(shù)羔杨,該函數(shù)的實(shí)現(xiàn)如下圖所示:
void memory_region_init_ram_from_file(MemoryRegion *mr,
struct Object *owner,
const char *name,
uint64_t size,
uint64_t align,
uint32_t ram_flags,
const char *path,
Error **errp)
{
Error *err = NULL;
memory_region_init(mr, owner, name, size);
mr->ram = true;
mr->terminates = true;
mr->destructor = memory_region_destructor_ram;
mr->align = align;
mr->ram_block = qemu_ram_alloc_from_file(size, mr, ram_flags, path, &err);
mr->dirty_log_mask = tcg_enabled() ? (1 << DIRTY_MEMORY_CODE) : 0;
if (err) {
mr->size = int128_zero();
object_unparent(OBJECT(mr));
error_propagate(errp, err);
}
}
顯然捌臊,初始化后,使用了qemu_ram_alloc_from_file
函數(shù)分配了ram_block
兜材,那么再往深處走一下理澎,其實(shí)這個(gè)函數(shù)就是比較簡單的調(diào)用了qemu_ram_alloc_from_fd
,而該函數(shù)的實(shí)現(xiàn)則:
RAMBlock *qemu_ram_alloc_from_fd(ram_addr_t size, MemoryRegion *mr,
uint32_t ram_flags, int fd,
Error **errp)
{
RAMBlock *new_block;
Error *local_err = NULL;
int64_t file_size;
/* Just support these ram flags by now. */
assert((ram_flags & ~(RAM_SHARED | RAM_PMEM)) == 0);
if (xen_enabled()) {
error_setg(errp, "-mem-path not supported with Xen");
return NULL;
}
if (kvm_enabled() && !kvm_has_sync_mmu()) {
error_setg(errp,
"host lacks kvm mmu notifiers, -mem-path unsupported");
return NULL;
}
if (phys_mem_alloc != qemu_anon_ram_alloc) {
/*
* file_ram_alloc() needs to allocate just like
* phys_mem_alloc, but we haven't bothered to provide
* a hook there.
*/
error_setg(errp,
"-mem-path not supported with this accelerator");
return NULL;
}
size = HOST_PAGE_ALIGN(size);
file_size = get_file_size(fd);
if (file_size > 0 && file_size < size) {
error_setg(errp, "backing store %s size 0x%" PRIx64
" does not match 'size' option 0x" RAM_ADDR_FMT,
mem_path, file_size, size);
return NULL;
}
new_block = g_malloc0(sizeof(*new_block));
new_block->mr = mr;
new_block->used_length = size;
new_block->max_length = size;
new_block->flags = ram_flags;
new_block->host = file_ram_alloc(new_block, size, fd, !file_size, errp);
if (!new_block->host) {
g_free(new_block);
return NULL;
}
ram_block_add(new_block, &local_err, ram_flags & RAM_SHARED);
if (local_err) {
g_free(new_block);
error_propagate(errp, local_err);
return NULL;
}
return new_block;
}
盡管該函數(shù)的實(shí)現(xiàn)頗長曙寡,但我們需要關(guān)系的大概也就兩句話:file_ram_alloc
和ram_block_add
矾端,前一句是實(shí)打?qū)嵉赜?code>qemu_ram_mmap影射了一段內(nèi)存并返回,而后一句則是把這段內(nèi)存使用RAMBlock
的形式保存卵皂,并插入全局的ram_list.blocks
中秩铆。
這樣系統(tǒng)的RAMBlock就初始化完畢了,那么如何把分配出來的內(nèi)存分配給KVM呢灯变?這里就主要是memory_region_add_subregion
做的事情了殴玛。
memory_region_add_subregion
首先講傳入的subregion
的container
成員設(shè)為mr
,然后就調(diào)用memory_region_update_container_subregions
函數(shù)。
memory_region_update_container_subregions
則把subregion
插入到其container
添祸,也就是爸爸的subregions_link
鏈表中去滚粟,緊接著就調(diào)用memory_region_transaction_commit
函數(shù),對所有address_spaces_link
調(diào)用address_space_set_flatview
刃泌,尋找掛載在AddressSpace
上的所有listener
凡壤,執(zhí)行address_space_update_topology_pass
,兩個(gè)FlatView逐條的FlatRange進(jìn)行對比耙替,以后一個(gè)FlatView為準(zhǔn)亚侠,如果前面FlatView的FlatRange和后面的不一樣,則對前面的FlatView的這條FlatRange進(jìn)行處理俗扇。
具體邏輯如下:
static void address_space_update_topology_pass(AddressSpace *as,
const FlatView *old_view,
const FlatView *new_view,
bool adding)
{
unsigned iold, inew;
FlatRange *frold, *frnew;
/* Generate a symmetric difference of the old and new memory maps.
* Kill ranges in the old map, and instantiate ranges in the new map.
*/
iold = inew = 0;
while (iold < old_view->nr || inew < new_view->nr) {
if (iold < old_view->nr) {
frold = &old_view->ranges[iold];
} else {
frold = NULL;
}
if (inew < new_view->nr) {
frnew = &new_view->ranges[inew];
} else {
frnew = NULL;
}
if (frold
&& (!frnew
|| int128_lt(frold->addr.start, frnew->addr.start)
|| (int128_eq(frold->addr.start, frnew->addr.start)
&& !flatrange_equal(frold, frnew)))) {
/* In old but not in new, or in both but attributes changed. */
if (!adding) {
flat_range_coalesced_io_del(frold, as);
MEMORY_LISTENER_UPDATE_REGION(frold, as, Reverse, region_del);
}
++iold;
} else if (frold && frnew && flatrange_equal(frold, frnew)) {
/* In both and unchanged (except logging may have changed) */
if (adding) {
MEMORY_LISTENER_UPDATE_REGION(frnew, as, Forward, region_nop);
if (frnew->dirty_log_mask & ~frold->dirty_log_mask) {
MEMORY_LISTENER_UPDATE_REGION(frnew, as, Forward, log_start,
frold->dirty_log_mask,
frnew->dirty_log_mask);
}
if (frold->dirty_log_mask & ~frnew->dirty_log_mask) {
MEMORY_LISTENER_UPDATE_REGION(frnew, as, Reverse, log_stop,
frold->dirty_log_mask,
frnew->dirty_log_mask);
}
}
++iold;
++inew;
} else {
/* In new */
if (adding) {
MEMORY_LISTENER_UPDATE_REGION(frnew, as, Forward, region_add);
flat_range_coalesced_io_add(frnew, as);
}
++inew;
}
}
}
可以看出硝烂,經(jīng)過一系列判斷之后,調(diào)用了MEMORY_LISTENER_UPDATE_REGION
宏铜幽,該宏則根據(jù)第四個(gè)參數(shù)索引應(yīng)該執(zhí)行的回調(diào)方法滞谢,這里如果傳入的是region_add
,則調(diào)用AddressSpace::region_add
方法除抛,對于kvm而言狮杨,則是調(diào)用了kvm_region_add
方法。
而該回調(diào)則是在初始化時(shí)調(diào)用kvm_init
時(shí)到忽,在初始化結(jié)束之后調(diào)用了kvm_memory_listener_register
將kvm的一系列函數(shù)注冊到address_space_memory
中的橄教。
話說回來,kvm_region_add又做了什么呢?最核心的其實(shí)就是用kvm_vm_ioctl(s, KVM_SET_USER_MEMORY_REGION, &mem)
將這段內(nèi)存扔給KVM啦颤陶。