QEMU中KVM的初始化調(diào)用路徑

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_allocram_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首先講傳入的subregioncontainer成員設(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啦颤陶。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末颗管,一起剝皮案震驚了整個(gè)濱河市陷遮,隨后出現(xiàn)的幾起案子滓走,更是在濱河造成了極大的恐慌,老刑警劉巖帽馋,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搅方,死亡現(xiàn)場離奇詭異,居然都是意外死亡绽族,警方通過查閱死者的電腦和手機(jī)姨涡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吧慢,“玉大人涛漂,你說我怎么就攤上這事〖焓” “怎么了匈仗?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長逢慌。 經(jīng)常有香客問我悠轩,道長,這世上最難降的妖魔是什么攻泼? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任火架,我火速辦了婚禮,結(jié)果婚禮上忙菠,老公的妹妹穿的比我還像新娘何鸡。我一直安慰自己,他們只是感情好牛欢,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布音比。 她就那樣靜靜地躺著,像睡著了一般氢惋。 火紅的嫁衣襯著肌膚如雪洞翩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天焰望,我揣著相機(jī)與錄音骚亿,去河邊找鬼。 笑死熊赖,一個(gè)胖子當(dāng)著我的面吹牛来屠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼俱笛,長吁一口氣:“原來是場噩夢啊……” “哼捆姜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起迎膜,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤泥技,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后磕仅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體珊豹,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年榕订,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了店茶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡劫恒,死狀恐怖贩幻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情两嘴,我是刑警寧澤丛楚,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站溶诞,受9級特大地震影響鸯檬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜螺垢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一喧务、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧枉圃,春花似錦功茴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至返劲,卻和暖如春玲昧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背篮绿。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工孵延, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人亲配。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓尘应,卻偏偏與公主長得像惶凝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子犬钢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容