热迁移技术-QEMU

news2024/11/29 20:34:29

社区有言Talk is cheep, show me the code,我们尽量低纬度描述技术。

代码和版本:

Qemu-5.0  #热迁移技术的实现者

Kernel-4.19 #提供kvm实现

热迁移的演进

Qemu有加载保存vm的功能,这是两个互补的操作。保存状态就是为每个vm中运行的设备保存状态,恢复vm就是相反的操作。

为了实现这个功能,这个过程qemu用相同的参数启动两次,在一个vm恢复状态,这个vm与保存的设备相同(也可以支持部分不同)。

一旦我们能够保存恢复(快照)vm,新功能也有支持的基础:迁移。Qemu能够在一个物理机迁移到另一台物理机。

接下来就是实时迁移功能。因为vm有很多运行状态(特别是RAM),而且它可能需要一段时间才能把所有状态从一台物理机迁移到另一台物理机。热迁移允许vm在状态迁移时继续运行,直到最后一部分状态传输时停止。Vm无响应的典型时间在几百毫秒以内。Downtime取决于多个因素,如带宽,vm产生脏页的速度,迁移内存水线,硬件性能。

Qemu热迁移模型

  1. 热迁移过程

Qemu热迁移过程

  1. 标记所有内存为脏页,首次迁移拷贝所有内存
  2. 迭代迁移新的脏页,直到降低到一定水线,可以一次性迁移过去。
  3. 暂停vm,一次性迁移剩余脏页,然后迁移设备状态,启动目的端虚拟机。

从Qemu  monitor或者libvirt的domain unix socket消息触发热迁移,源端进程执行precopy,对vm堆栈、内存进行备份用于异常恢复,创建传输通道,迁移过程中dirty page bitmap(用于标识vm里内存动态变化的部分)通过copy、send、recv、restore,在源和目的qemu进程间交互。

传输channel:

Fd 虚拟文件,进程间通信,Fd常用。

tcp socket网络

Rdma 网络

unix socket 进程间通信

exec stdin|sdout 进程间通信

这些可以承载迁移流的文件被抽象成QEMUFile类型(migration/qemu-file.h),在大多数情况会连接到一个子类型QIOChannel(io/)

一个独立的线程作为迁移的执行者和入口,实现迁移功能

se就是savevm_state的链表节点,

typedef struct SaveStateEntry {

    QTAILQ_ENTRY(SaveStateEntry) entry;

    char idstr[256];

    uint32_t instance_id;

    int alias_id;

    int version_id;

    /* version id read from the stream */

    int load_version_id;

    int section_id;

    /* section id read from the stream */

    int load_section_id;

    const SaveVMHandlers *ops; ====  迭代设备必须实现的回调函数

    const VMStateDescription *vmsd;  === 非迭代设备数据结构

    void *opaque;

    CompatEntry *compat;

    int is_ram;

} SaveStateEntry;

QTAILQ_HEAD_INITIALIZER(savevm_state.handlers)

迁移核心代码是公共设施,各个设备实现自己的数据结构和回调,对于迁移的所有对象我们都描述为设备,要迁移的不同设备,保存在一个链表(savevm_state)。迭代设备注册SaveVMHandlers 类型,非迭代设备注册VMStateDescription

  1. 迁移数据结构

VMState 宏(为了方便描述代指---》VMStateDescription数据结构)

hw/input/pckbd.c

static const VMStateDescription vmstate_kbd = {

    .name = "pckbd",

    .version_id = 3,

    .minimum_version_id = 3,

    .fields = (VMStateField[]) { ========== 保存设备寄存器值

        VMSTATE_UINT8(write_cmd, KBDState),

        VMSTATE_UINT8(status, KBDState),

        VMSTATE_UINT8(mode, KBDState),

        VMSTATE_UINT8(pending, KBDState),

        VMSTATE_END_OF_LIST()

    }};

vmstate_register(NULL, 0, &vmstate_kbd, s); ===== 设备状态结构注册到链表se

 ----》 dc->vmsd = &vmstate_kbd_isa; ====== 如果是qdev类型的,可以直接在类里注册

一个设备简单的保持和加载是不够的,我们要保证它的正确性。例如:在保存CPU前,要求KVM把当前状态复制到QEMU,

在加载时,需要一种方法告诉KVM我们要从QEMUFile加载CPU状态。

要执行的操作函数位于vmstate

    int (*pre_load)(void *opaque);

    int (*post_load)(void *opaque, int version_id);

    int (*pre_save)(void *opaque);

int (*post_save)(void *opaque);

VMState宏可以保证设备数据可移植格式化(大端字节序),可以嵌套VMStateDescriptions 子结构

嵌套的子设备状态结构,用于添加新数据。虽然也是一个完整的vmstate结构,特殊性在于可以控制是否发送。

在接收端,如果发现一个解析不了的子VMState,就迁移失败。

static bool ide_drive_pio_state_needed(void *opaque){

    IDEState *s = opaque;

    return ((s->status & DRQ_STAT) != 0)  || (s->bus->error_status & BM_STATUS_PIO_RETRY);}

const VMStateDescription vmstate_ide_drive_pio_state = {

    .name = "ide_drive/pio_state",

    .version_id = 1,

    .minimum_version_id = 1,

    .pre_save = ide_drive_pio_pre_save,

    .post_load = ide_drive_pio_post_load,

    .needed = ide_drive_pio_state_needed,

    .fields = (VMStateField[]) {

        VMSTATE_INT32(req_nb_sectors, IDEState),

        VMSTATE_VARRAY_INT32(io_buffer, IDEState, io_buffer_total_len, 1,

                             vmstate_info_uint8, uint8_t),

        VMSTATE_INT32(cur_io_buffer_offset, IDEState),

        VMSTATE_INT32(cur_io_buffer_len, IDEState),

        VMSTATE_UINT8(end_transfer_fn_idx, IDEState),

        VMSTATE_INT32(elementary_transfer_size, IDEState),

        VMSTATE_INT32(packet_transfer_size, IDEState),

        VMSTATE_END_OF_LIST()

    }};

const VMStateDescription vmstate_ide_drive = {

    .name = "ide_drive",

    .version_id = 3,

    .minimum_version_id = 0,

    .post_load = ide_drive_post_load,

    .fields = (VMStateField[]) {

        .... several fields ....

        VMSTATE_END_OF_LIST()

    },

    .subsections = (const VMStateDescription*[]) {

        &vmstate_ide_drive_pio_state,

        NULL

    }};

例如这个pio的状态sectio,在保存/发送的时候检测DRQ_STAT。如果未启用说明这个子设备状态不需要发送。

有时不需要某个字段,保持兼容的做法是,用添加新的子设备状态或者使用_TEST宏,如 VMSTATE_UINT32 -> VMSTATE_UINT32_TEST

迭代设备迁移一般不用VMState宏,而是用SaveVMHandlers,并用qemu_put_*/qemu_get_*宏把数据读写到IO流。

迭代设备必须提供SaveVMHandlers:

  • int (*save_setup)(QEMUFile *f, void *opaque);
  • int (*load_setup)(QEMUFile *f, void *opaque);
  • void (*save_live_pending)(QEMUFile *f, void *opaque,----》指示还有多少必须保存的数据,用来确定何时停CPU完成迁移

uint64_t threshold_size,

uint64_t *res_precopy_only, //用于在预拷贝阶段或停止状态下必须迁移的数据,即在目标虚拟机启动之前

uint64_t *res_compatible, //用于在任何阶段都可能迁移的数据

uint64_t *res_postcopy_only); //用于在postcopy阶段或停止状态(即源虚拟机停止后)必须迁移的数据

                                          //这三类加起来就是要拷贝的数据总量

  • int (*save_live_iterate)(QEMUFile *f, void *opaque); ====迭代迁移函数,在pending调用后调用,每次生成、发送一个数据块。
  • int (*save_live_complete_precopy)(QEMUFile *f, void *opaque);   //剩余可以一次性迁移的最后一部分数据
  • LoadStateHandler *load_state; 
  • int (*load_cleanup)(void *opaque);

像CPU设备,要支持对vmstate填充正确的值,例如,当使用KVM时,在保存CPU状态之前,要求KVM将它正在使用的状态复制到QEMU,相反,加载恢复时,需要一个方法告诉KVM加载我们刚刚从QEMUfile加载的CPU状态

函数回调定义在vmstate结构中:

Example: hpet.c

当迁移数据和保存的设备数据不一致,用VMSTATE_WITH_TMP 填充到临时结构传输。

如果使用在初始化之外使用内存api更新内存,例如响应vm。在post_load调用回调示例

  1. memory_region_add_subregion()
  2. memory_region_del_subregion()
  3. memory_region_set_readonly()
  4. memory_region_set_nonvolatile()
  5. memory_region_set_enabled()
  6. memory_region_set_address()
  7. memory_region_set_alias_offset()

///注册迭代设备方法到链表

int register_savevm_live(const char *idstr,      // 标识设备    

                         int instance_id,        //标识vm

                         int version_id,          //标识设备版本,兼容性控制

                         SaveVMHandlers *ops,    //保存和加载用的函数指针

                         void *opaque);          //函数指针的参数

每个设备必须注册两个函数,一个用于保存,一个用于加载回去。

这个函数在SaveVMHandlers*参数是save_state和load_state功能,load_state根据version_id参数判断接收的什么状态格式,save_state没有version_id,因为它永远使用最新版本。VMstate宏把数据保存为raw格式,这个结构可以用构造数据替换。

根据数据是否变化可以分为两类,一种是数据量大,并且不断变化的数据,例如内存、支持热迁的存储。需要注册SaveVMHandlers

一种是qemu本身相关,不会变化的设备状态,而且能在迁移最后阶段完成的,如大多数设备。这类模块注册VMStateDescriotion结构并保存在迁移链表SaveStateEntry节点的vmsd成员。

设备加载有时候有依赖关系,比如pci设备必须在pci bus加载后加载,我们需要优先级控制加载顺序,在VMStateDescription 中MigrationPriority枚举结构指示这个level。

迭代设备和非迭代设备,都会在初始化的时候构造SaveStateEntry添加到 savevm_state.handlers链表上。

传统方式

内存迁移

像内存一样需要迭代迁移的设备必须提供方法:

  • ``save_setup``函数初始化数据结构和发送设备的第一个section,对于RAM就是内存块和大小.-->保存
  • ``load_setup``初始化目的端
  • ``save_live_pending`` 反复调用的函数必须指出需要迭代保存的数据量。迁移的核心代码需要根据它确定什么时候暂停cpu,完成迁移。
  • ``save_live_iterate`` ``save_live_pending``后,仍有重要数据要发送时调用。它应该发送一个数据块,直到流带宽限制告诉它停止。每个调用生成一个section。
  • ``save_live_complete_precopy`` 传输包含所有剩余的数据的最后一个section
  • ``load_state`` 用于加载所有保存方法生成的sections
  • ``cleanup`` 在迁移结束时,用于保存和加载-->恢复

ram_save_setup过程是初始化热迁移相关的数据,计算vm总的内存大小,并写入QEMUFile,然后将ram_list的     所有block发送,用RAM_SAVE_FLAG_EOS作为数据分割标识

热迁移第一阶段初始化,为内存分配脏页标记的bitmap、开启脏页记录、把所有ramblock写入迁移流

ram_save_setup

    ram_init_bitmaps--分配一个全局的bitmap标识page是否为脏页

        bitmap_set  设置所有页为脏

    memory_global_dirty_log_start   设置全局变量global_dirty_tracking标识开始脏页标记

        MEMORY_LISTENER_CALL_GLOBAL(log_global_start, Forward)  然后调用回调log_global通知感兴趣的模块开始脏页标记

        memory_region_transaction_commit将内存变化告诉KVM,kvm在ioctl(KVM_SET_USER_MEMORY_REGION)中

                                        加上KVM_MEM_LOG_DIRTY_PAGES标记,kvm把vm内存设为写保护或者用硬件PML记录脏页。

热迁移第二阶段迭代迁移

ram_save_pending,同步kvm脏页,确定好当前脏页数量,然后用ram_save_iterate发送脏页,每一轮迭代完migration_rate_limit计算阈值max_size

ram_save_pending(QEMUFile *f, void *opaque, uint64_t max_size, //当前带宽和downtime下的最大发送量

                             uint64_t *res_precopy_only,

                             uint64_t *res_compatible,

                             uint64_t *res_postcopy_only)

        migration_bitmap_sync_precopy 如果小于max_size,从KVM同步脏页信息。

       *res_precopy_only += remaining_size 如果大于max_size,说明脏页数比较多,计算出脏页数即可

最大发送量在migration_thread中计算出来:

migration_rate_limit

    current_bytes = qemu_ftell(s->to_dst_file) + ram_counters.multifd_bytes;

    transferred = current_bytes - s->iteration_initial_bytes;

    time_spent = current_time - s->iteration_start_time;

    bandwidth = (double)transferred / time_spent;

    s->threshold_size = bandwidth * s->parameters.downtime_limit;

int qemu_savevm_state_iterate(QEMUFile *f, bool postcopy)

{

    SaveStateEntry *se;

    QTAILQ_FOREACH(se, &savevm_state.handlers, entry) {

        save_section_header(f, se, QEMU_VM_SECTION_PART);设置数据段头标标识

        ret = se->ops->save_live_iterate(f, se->opaque); ====》ram_save_iterate

        save_section_footer(f, se); 设置数据尾分割标识

    }

}

ram_save_iterate 循环发送脏页

    pages = ram_find_and_save_block(rs); 找到并发送脏页

        find_dirty_block 找到下一个脏页

        ram_save_host_page 发送

            ram_save_page

                qemu_put_buffer

    如果执行时间大于MAX_WAIT( == 50ms)退出循环,因为获取时间比较费时,所以迭代64次检查一下

    结束时设置RAM_SAVE_FLAG_EOS分割标识

热迁移第三阶段停机迁移,当要迁移量小于阈值,停止虚拟机,进行最后的脏页同步和发送,然后调用各个设备注册的回调发送设备状态

migration_completion

   vm_stop_force_state(RUN_STATE_FINISH_MIGRATE)停虚拟机,并改运行状态

    qemu_savevm_state_complete_precopy

        qemu_savevm_state_complete_precopy_iterable

            save_section_header(f, se, QEMU_VM_SECTION_END);

            se->ops->save_live_complete_precopy ===》ram_save_complete

            save_section_footer(f, se);

    qemu_savevm_state_complete_precopy_non_iterable

        save_section_header(f, se, QEMU_VM_SECTION_FULL);

        vmstate_save(f, se, vmdesc);

            vmstate_save_state_v  保存vmstate到json,然后发出去

                vmsd->pre_save(opaque)

                vmstate_save         

                vmstate_subsection_save(f, vmsd, opaque, vmdesc)

                vmsd->post_save(opaque)

        save_section_footer(f, se)

ram_save_complete

    migration_bitmap_sync_precopy 获取KVM侧脏页

    pages = ram_find_and_save_block(rs); 找到并发送脏页

        find_dirty_block 找到下一个脏页

        flush_compressed_data 发送

            ram_save_page

                qemu_put_buffer

    结束时设置RAM_SAVE_FLAG_EOS分割标识

迁移结束之后计算时间

migration_iteration_finish

可以通过api获取downtime

脏页同步过程

1、QEMU为所有内存分配脏页位图全部设置为1。ram_list_init_bitmaps,保存在RAMBlock->bmap

2、QEMU通过ioctl(KVM_SET_USER_MEMORY_REGION)与新虚拟机的内存布局,将内存flags加上KVM_MEM_LOG_DIRTY_PAGES标识,KVM据此可以记录虚拟机内存的写访问

3、热迁移开始,热迁移线程从RAMBlock的bmap成员中选择脏页发送到目的端,虚拟机可以自由访问内存,其中的写内存会被KVM记录下来。

4、QEMU记录的脏页数据已经发送到只剩最后的max_size的时候,调用migration_bitmap_sync进行脏页同步,该函数最终会调用到

ioctl(KVM_GET_DIRTY_LOG)将KVM的脏页记录获取保存到ram_list.dirty_memory中(通过函数kvm_log_sync完成),然后将

ram_list.dirty_memory的脏页信息复制到RAMBlock的bmap成

员中。

5、如果脏页数据大于max_size,则进入第3)步开始迭代发送的过

全局变量ram_list的dirty_memory成员用来记录虚拟机的内存脏页,由于可能有多个地方使用这个脏页,所以这个成员是一个数组,可以记

录DIRTY_MEMORY_VGA、DIRTY_MEMORY_CODE、DIRTY_MEMORY_MIGRATION三种脏页,其中

DIRTY_MEMORY_CODE用于tcg,DIRTY_MEMORY_MIGRATION用于热迁移。

typedef struct {

    struct rcu_head rcu;

    unsigned long *blocks[];

} DirtyMemoryBlocks;

typedef struct RAMList {

    QemuMutex mutex;

    RAMBlock *mru_block;

    /* RCU-enabled, writes protected by the ramlist lock. */

    QLIST_HEAD(, RAMBlock) blocks;

    DirtyMemoryBlocks *dirty_memory[DIRTY_MEMORY_NUM];

    uint32_t version;

    QLIST_HEAD(, RAMBlockNotifier) ramblock_notifiers;

} RAMList;

extern RAMList ram_list;

分配空间

static void dirty_memory_extend(ram_addr_t old_ram_size,

                                ram_addr_t new_ram_size)

{

    ram_addr_t old_num_blocks = DIV_ROUND_UP(old_ram_size,

                                             DIRTY_MEMORY_BLOCK_SIZE);

    ram_addr_t new_num_blocks = DIV_ROUND_UP(new_ram_size,

                                             DIRTY_MEMORY_BLOCK_SIZE);

    int i;

    /* Only need to extend if block count increased */

    if (new_num_blocks <= old_num_blocks) {

        return;

    }

    for (i = 0; i < DIRTY_MEMORY_NUM; i++) {

        DirtyMemoryBlocks *old_blocks;

        DirtyMemoryBlocks *new_blocks;

        int j;

        old_blocks = qatomic_rcu_read(&ram_list.dirty_memory[i]);

        new_blocks = g_malloc(sizeof(*new_blocks) +

                              sizeof(new_blocks->blocks[0]) * new_num_blocks);

        if (old_num_blocks) {

            memcpy(new_blocks->blocks, old_blocks->blocks,

                   old_num_blocks * sizeof(old_blocks->blocks[0]));

        }

        for (j = old_num_blocks; j < new_num_blocks; j++) {

            new_blocks->blocks[j] = bitmap_new(DIRTY_MEMORY_BLOCK_SIZE);

        }

        qatomic_rcu_set(&ram_list.dirty_memory[i], new_blocks);

        if (old_blocks) {

            g_free_rcu(old_blocks, rcu);

        }

    }

}

dirty_memory_extend函数首先计算总共需要多少个block,block个 数为RAM的大小除以DIRTY_MEMORY_BLOCK_SIZE,如果本次内存 小于等于上次,说明不需要扩展,直接返回,否则需要扩展。 接着为3种脏页记录类型分配空间,i表示当前处理的类型。在替换 老的DirtyMemoryBlocks时使用了RCU机制,首先获取 ram_list.dirty_memory[i],分配新的DirtyMemoryBlocks空间,并将老的 脏页数据复制到新分配的空间中,然后分配新DirtyMemoryBlocks的 blocks数组,每一个block大小为DIRTY_MEMORY_BLOCK_SIZE,最 后将ram_list.dirty_memory[i]替换成新分配的,并删掉老的 DirtyMemoryBlocks。

析KVM的脏页是如何同步到ram_list.memory[i]中的脏页位图

migration_bitmap_sync

   memory_global_dirty_log_sync

        memory_region_sync_dirty_bitmap

            listener->log_sync_global(listener) === 》kvm_log_sync==》将KVM的脏页记录获取保存到 ram_list.dirty_memory

    ramblock_sync_dirty_bitmap

        cpu_physical_memory_sync_dirty_bitmap===》 ram_list.dirty_memory[DIRTY_MEMORY_MIGRATION]->blocks中的脏页信息是复制到热迁移的迁移位图migration_bitmap_rcu->bmap中

kvm_log_sync

    kvm_physical_sync_dirty_bitmap

        kvm_slot_get_dirty_log

            kvm_vm_ioctl(s, KVM_GET_DIRTY_LOG, &d) === 获取KVM脏页

        kvm_slot_sync_dirty_pages

            cpu_physical_memory_set_dirty_lebitmap(slot->dirty_bmap, start, pages)

static inline void cpu_physical_memory_set_dirty_lebitmap(unsigned long *bitmap,

                                                          ram_addr_t start,

                                                          ram_addr_t pages)

{

    unsigned long i, j;

    unsigned long page_number, c;

    hwaddr addr;

    ram_addr_t ram_addr;

    unsigned long len = (pages + HOST_LONG_BITS - 1) / HOST_LONG_BITS;

    unsigned long hpratio = qemu_real_host_page_size() / TARGET_PAGE_SIZE;

    unsigned long page = BIT_WORD(start >> TARGET_PAGE_BITS);

    /* start address is aligned at the start of a word? */

    if ((((page * BITS_PER_LONG) << TARGET_PAGE_BITS) == start) &&

        (hpratio == 1)) {

        unsigned long **blocks[DIRTY_MEMORY_NUM];

        unsigned long idx;

        unsigned long offset;

        long k;

        long nr = BITS_TO_LONGS(pages);

        idx = (start >> TARGET_PAGE_BITS) / DIRTY_MEMORY_BLOCK_SIZE;

        offset = BIT_WORD((start >> TARGET_PAGE_BITS) %

                          DIRTY_MEMORY_BLOCK_SIZE);

        WITH_RCU_READ_LOCK_GUARD() {

            for (i = 0; i < DIRTY_MEMORY_NUM; i++) {

                blocks[i] =

                    qatomic_rcu_read(&ram_list.dirty_memory[i])->blocks;

            }

            for (k = 0; k < nr; k++) {

                if (bitmap[k]) {

                    unsigned long temp = leul_to_cpu(bitmap[k]);

                    qatomic_or(&blocks[DIRTY_MEMORY_VGA][idx][offset], temp);

                    if (global_dirty_tracking) {

                        qatomic_or(

                                &blocks[DIRTY_MEMORY_MIGRATION][idx][offset],

                                temp);

                        if (unlikely(

                            global_dirty_tracking & GLOBAL_DIRTY_DIRTY_RATE)) {

                            total_dirty_pages += ctpopl(temp);

                        }

                    }

                    if (tcg_enabled()) {

                        qatomic_or(&blocks[DIRTY_MEMORY_CODE][idx][offset],

                                   temp);

                    }

                }

                if (++offset >= BITS_TO_LONGS(DIRTY_MEMORY_BLOCK_SIZE)) {

                    offset = 0;

                    idx++;

                }

            }

        }

        xen_hvm_modified_memory(start, pages << TARGET_PAGE_BITS);

    } else {

    }

}

流结构

迁移数据流结构与关键字和字节序无关。

  • Header
    • Magic
    • Version
    • VM configuration section
      • Machine type
      • Target page bits
  • Sections列表,每个sectio包含一个设备或者一个设备保存的迭代
    • section type
    • section id
    • ID string (First section of each device)
    • instance id (First section of each device)
    • version id (First section of each device)
    • <device data>
    • Footer mark
  • EOF mark
  • VMDescription  有分析的描述信息的Json

对于不迭代的设备有一个section,迭代设备有初始化、最后一部分、一组介于两者之间的section。通用代码的完整性检查很少,主要依赖设备自己,当接收的数据比预期的多或者少时,提供一点保护。

例如:一个场景中,错误码指示迁移检查遇到IO错误,排查后发现原因是存储网通道有丢包现象。

后复制拷贝方式

正常迁移只需要单向流,需要双向流向时可以创建返回路径,主要用于postcopy,也用于迁移结束时返回给源端成功标识。

qemu_file_get_return_path(QEMUFile* fwdpath) 返回QEMUFIle*的指针

源端

Forward path - written by migration thread Return path - opened by main thread, read by return-path thread

目的端

Forward path - read by main thread Return path - opened by main thread, written by main thread AND postcopy thread (protected by rp_mutex)

当虚拟机内存变化大,始终降不到水线下,内存脏页无法收敛或者需要很久收敛,迁移无法完成。Qemu提出postcopy迁移模式,传统迁移称为precopy。区别是postcopy的脏页拷贝在启动之后还会继续,迁移流量和时间有限的,但是任何一端失败或者网络故障之后无法恢复vm。

Postcopy过程:

  1. 迁移设备状态
  2. 标脏所有内存,拷贝所有内存到目的端,启动vm
  3. 当目的端vm访问到内存脏页时,发生缺页异常,qemu从源端拷贝脏页对应内存。

Monitor开启postcopy方法:

migrate_set_capability postcopy-ram on

migrate_start_postcopy

  1. 命令行执行示例,

两边都放一个qcow2磁盘或者使用共享存储

B:/usr/libexec/qemu-kvm -m 2G --enable-kvm -cpu host  -incoming tcp:192.168.180.130:6666

A:/usr/libexec/qemu-kvm -m 2G --enable-kvm -cpu host  -monitor stdio

A: migrate tcp:192.168.180.130:6666

migrate_set_speed 1g

  1. 主要Api

Monitor cmd

(Qmp/Hmp)

info migrate

info migrate_capabilities

info migrate_parameters

获取迁移信息

migrate

迁移到对端

migrate_cancel

Migrate_continue

从paused恢复迁移

Migrate_incoming

开始传入迁移,使用-incoming启动qemu

Migrate_recover

迁移恢复

Migrate_pause

仅支持postcopy迁移模式

migrate_set_capability

migrate_set_parameter 

migrate_start_postcopy

Event

MIGRATION 

迁移发送status

MIGRATION_PASS

迁移同步时,从源端发送

UNPLUG_PRIMARY

Vm未插入设备

调试

调试工具:scripts/analyze-migration.py

示例:

$ qemu-system-x86_64 -display none -monitor stdio

(qemu) migrate "exec:cat > mig"

(qemu) q

$ ./scripts/analyze-migration.py -f mig

{ "ram (3)": {

"section sizes": {

"pc.ram": "0x0000000008000000",

...

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/991868.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

不同的二叉搜索树【动态规划】

不同的二叉搜索树 给你一个整数 n &#xff0c;求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种&#xff1f;返回满足题意的二叉搜索树的种数。 class Solution {//testpublic int numTrees(int n) {//初始化 dp 数组int[] dp new int[n 1];//初始化…

像素空间文生图之Imagen原理详解

论文:Photorealistic Text-to-Image Diffusion Models with Deep Language Understanding项目地址:https://imagen.research.google/代码(非官方):https://github.com/deep-floyd/IF模型权重:https://huggingface.co/DeepFloyd/IF-I-XL-v1.0🤗关注公众号 funNLPer 白嫖…

初识网络的发展史、通信基础和原理

目录 一.网络的发展史 二.网络通信基础 2.1IP地址 2.2端口号 2.3认识协议 2.3.1协议是什么&#xff1f; 2.3.2为什么需要协议&#xff1f; 2.3.3OSI模型和TCP/IP体系结构 三.网络通信的原理 总结 &#x1f381;个人主页&#xff1a;tq02的博客_CSDN博客-C语言,Java,J…

如何在海外通过A/B测试来优化应用

在App Store和Google Play上&#xff0c;视觉资产是应用商店优化(ASO)的重要组成部分。我们的游戏图标、屏幕截图和预览视频具有很高的转化潜力。 1、将应用页面元素的一个或多个变体与默认元素进行竞争。 借助A/B测试&#xff0c;我们可以轻松、客观地发现访问者的偏好。这样…

PPT架构师架构技能图

PPT架构师架构技能图 目录概述需求&#xff1a; 设计思路实现思路分析1.软素质2.核心输出&#xff08;office输出&#xff09; 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,ma…

MFC中的类继承图的基本框架

一、类继承关系 从图中可知&#xff0c;在MFC中大多数的类都派生于CObject类&#xff0c;它的主要作用是为子类提供一些基本的功能&#xff0c;这些派生类构成了MFC应用程序的基本框架&#xff0c;它们各自的功能描述如表1所示。 派生类 功能描述 CCmdTarget 用于处理用户请…

React-Hooks 和 React-Redux

Hooks基本介绍------------------------- Hooks&#xff1a;钩子、钓钩、钩住&#xff0c; Hook 就是一个特殊的函数&#xff0c;让你在函数组件中获取状态等 React 特性 &#xff0c;是 React v16.8 中的新增功能 作用&#xff1a;为函数组件提供状态、生命周期等原本 class …

51、基于注解方式开发Spring WebFlux,实现生成背压数据,就是实现一直向客户端发送消息

★ Spring WebFlux的两种开发方式 1. 采用类似于Spring MVC的注解的方式来开发。此时开发时感觉Spring MVC差异不大&#xff0c;但底层依然是反应式API。2. 使用函数式编程来开发★ 基于注解开发Spring WebFlux 开发上变化并不大&#xff0c;主要是处理方法的返回值可使用Mon…

Devos勒索病毒:网络安全的新威胁,勒索病毒解密,数据恢复

随着信息技术的飞速发展&#xff0c;网络安全问题日益凸显。近年来&#xff0c;一种名为Devos的勒索病毒在全球范围内肆虐&#xff0c;给企业和个人带来了极大的损失。本文将详细介绍Devos勒索病毒的特点、传播途径以及预防和应对措施&#xff0c;帮助大家更好地认识和防范这一…

针对JavaScript混淆加密,JShaman推出新功能

JShaman英文版在最新的一次更新时&#xff0c;增加了新功能&#xff1a; JavaScript代码混淆加密完成后&#xff0c;可以显示各功能耗时、处理的AST节点数量&#xff0c; 以此可知对代码做了哪些保护处理。 如上图所示&#xff0c;在此例中&#xff0c;对代码共进行了23项混淆…

Python标准数据类型-List(列表)

✅作者简介&#xff1a;CSDN内容合伙人、阿里云专家博主、51CTO专家博主、新星计划第三季python赛道Top1&#x1f3c6; &#x1f4c3;个人主页&#xff1a;hacker707的csdn博客 &#x1f525;系列专栏&#xff1a;零基础入门篇 &#x1f4ac;个人格言&#xff1a;不断的翻越一座…

无涯教程-JavaScript - IMPRODUCT函数

描述 IMPRODUCT函数以x yi或x yj文本格式返回1到255个复数的乘积。两个复数的乘积为- $$(A BI)(C DI)(AC-BD)(A B)1 $$ 语法 IMPRODUCT (inumber1, [inumber2] ...)争论 Argument描述Required/OptionalInumber11 to 255 complex numbers to multiply.Required[inumbe…

xCode14.3.1运行MonkeyDev出现“Executable Not Found“的解决办法

安装MonkeyDev遇到的坑 环境&#xff1a;Xcode Version 14.3.1 (14E300c) 错误提示 is not a valid path to an executable file. 报错 /Users/xxxx//Library/Developer/Xcode/DerivedData/MonTest-ccparhdyzjuqhjdergwrngpfwwoh/Build/Products/Debug-iphoneos/MonTest.app…

基于大规模测量和多任务深度学习的电子鼻系统目标识别、浓度预测和状态判断

Target discrimination, concentration prediction, and status judgment of electronic nose system based on large-scale measurement and multi-task deep learning 摘要 为了实现响应特征的自动提取&#xff0c;简化模型的训练和应用过程&#xff0c;设计了一种双块知识…

python3网络爬虫--爬取B站视频弹幕(附源码)

文章目录 一&#xff0e;前言二&#xff0e;配置Protobuf 环境&生成编译文件1&#xff0e;配置Protobuf 环境2&#xff0e;生成编译文件 三&#xff0e;解析弹幕四&#xff0e;自动解析弹幕五&#xff0e;总结六&#xff0e;参考 本篇博文记录一下爬取B站弹幕的主要思路以及…

Pandas模块:Python科学计算神器之一

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ 🐴作者:秋无之地 🐴简介:CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作,主要擅长领域有:爬虫、后端、大数据开发、数据分析等。 🐴欢迎小伙伴们点赞👍🏻、收藏⭐️、…

SpotBugs检查java代码:不应该依赖平台默认编码(DM_DEFAULT_ENCODING)

https://spotbugs.readthedocs.io/en/latest/bugDescriptions.html#internationalization-i18n 一个字节转换为字符串&#xff08;或字符串转化为字节&#xff09;的函数调用没有明确指明编码&#xff0c;而是依赖平台默认的编码&#xff0c;这可能导致应用在不同平台上的行为…

mac电脑做为开发机的一些初始化操作

Terminal设置 风格设置 修改Terminal提示符 /etc/zshrc 此处控制&#xff0c;大概70行左右 PS1"%n%m %1~ %# "目录颜色设置 编辑文件 vim ~/.bash_profile输入以下内容 export LS_OPTIONS--colorauto # 如果没有指定&#xff0c;则自动选择颜色 export CLICOLOR…

【C++漂流记】一文搞懂类与对象的封装

本篇文章主要说明了类与对象中封装的有关知识&#xff0c;包括属性和行为作为整体、访问权限、class与struct的区别、成员属性的私有化&#xff0c;希望这篇文章可以帮助你更好的了解类与对象这方面的知识。 文章目录 一、属性和行为作为整体二、访问权限三、class与struct的区…

spark集成hudi

启动spark-shell spark-shell \ > --jars /opt/software/hudi-spark3.1-bundle_2.12-0.12.0.jar \ > --conf spark.serializerorg.apache.spark.serializer.KryoSerializer\ > --conf spark.sql.extensionsorg.apache.spark.sql.hudi.HoodieSparkSessionExtension2…