Make migration work with hotplug
PATCH v3 11/16 savevm: Migrate RAM based on name/offset
In reply to Alex WilliamsonSynchronize RAM blocks with the target and migrate using name/offset pairs. This ensures both source and target have the same view of RAM and that we get the right bits into the right slot.
---
arch_init.c | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----- vl.c | 2 + 2 files changed, 108 insertions(+), 12 deletions(-)
arch_init.c +107 -11
@@ -113,20 +113,33 @@ static int ram_save_block(QEMUFile *f)while (addr < total_ram) {if (cpu_physical_memory_get_dirty (current_addr,MIGRATION_DIRTY_FLAG )) { +RAMBlock *block; +ram_addr_t offset;uint8_t *p;cpu_physical_memory_reset_dirty (current_addr, current_addr +TARGET_PAGE_SIZE ,MIGRATION_DIRTY_FLAG ); - p =qemu_get_ram_ptr (current_addr); + QLIST_FOREACH(block, &ram_list.blocks, next) { +if (current_addr - block->offset < block->length) +break ; + } + offset = current_addr - block->offset; + p = block->host + offset;if (is_dup_page (p, *p)) { -qemu_put_be64 (f, current_addr |RAM_SAVE_FLAG_COMPRESS ); +qemu_put_be64 (f, offset |RAM_SAVE_FLAG_COMPRESS ); +qemu_put_byte (f,strlen (block->idstr)); +qemu_put_buffer (f, (uint8_t *)block->idstr, +strlen (block->idstr));qemu_put_byte (f, *p); bytes_sent =1 ; }else { -qemu_put_be64 (f, current_addr |RAM_SAVE_FLAG_PAGE ); +qemu_put_be64 (f, offset |RAM_SAVE_FLAG_PAGE ); +qemu_put_byte (f,strlen (block->idstr)); +qemu_put_buffer (f, (uint8_t *)block->idstr, +strlen (block->idstr));qemu_put_buffer (f, p,TARGET_PAGE_SIZE ); bytes_sent =TARGET_PAGE_SIZE ; }
@@ -196,6 +209,7 @@ int ram_save_live(Monitor *mon, QEMUFile *f, int stage, void *opaque) }if (stage ==1 ) { +RAMBlock *block;uint64_t total_ram =ram_bytes_total (); bytes_transferred =0 ;
@@ -210,6 +224,12 @@ int ram_save_live(Monitor *mon, QEMUFile *f, int stage, void *opaque)cpu_physical_memory_set_dirty_tracking (1 );qemu_put_be64 (f, total_ram |RAM_SAVE_FLAG_MEM_SIZE ); + + QLIST_FOREACH(block, &ram_list.blocks, next) { +qemu_put_byte (f,strlen (block->idstr)); +qemu_put_buffer (f, (uint8_t *)block->idstr,strlen (block->idstr)); +qemu_put_be64 (f, block->length); + } } bytes_transferred_last = bytes_transferred;
@@ -257,7 +277,7 @@ int ram_load(QEMUFile *f, void *opaque, int version_id)ram_addr_t addr;int flags; -if (version_id !=3 ) { +if (version_id <3 || version_id >4 ) {return -EINVAL ; }
@@ -268,23 +288,99 @@ int ram_load(QEMUFile *f, void *opaque, int version_id) addr &=TARGET_PAGE_MASK ;if (flags &RAM_SAVE_FLAG_MEM_SIZE ) { -if (addr !=ram_bytes_total ()) { -return -EINVAL ; +if (version_id ==3 ) { +if (addr !=ram_bytes_total ()) { +return -EINVAL ; + } + }else { +/* Synchronize RAM block list */ +char id[256 ]; +ram_addr_t length; +ram_addr_t total_ram_bytes = addr; + +while (total_ram_bytes) { +RAMBlock *block; +uint8_t len; + + len =qemu_get_byte (f); +qemu_get_buffer (f, (uint8_t *)id, len); + id[len] =0 ; + length =qemu_get_be64 (f); + + QLIST_FOREACH(block, &ram_list.blocks, next) { +if (!strncmp (id, block->idstr,sizeof (id))) { +if (block->length != length) +return -EINVAL ; +break ; + } + } + +if (!block) { +if (!qemu_ram_alloc (NULL , id, length)) +return -ENOMEM ; + } + + total_ram_bytes -= length; + } } }if (flags &RAM_SAVE_FLAG_COMPRESS ) { -uint8_t ch =qemu_get_byte (f); -memset (qemu_get_ram_ptr (addr), ch,TARGET_PAGE_SIZE ); +void *host; +uint8_t ch; + +if (version_id ==3 ) { + host =qemu_get_ram_ptr (addr); + }else { +RAMBlock *block; +char id[256 ]; +uint8_t len; + + len =qemu_get_byte (f); +qemu_get_buffer (f, (uint8_t *)id, len); + id[len] =0 ; + + QLIST_FOREACH(block, &ram_list.blocks, next) { +if (!strncmp (id, block->idstr,sizeof (id))) +break ; + } +if (!block) +return -EINVAL ; + + host = block->host + addr; + } + ch =qemu_get_byte (f); +memset (host, ch,TARGET_PAGE_SIZE );#ifndef _WIN32if (ch ==0 && (!kvm_enabled () ||kvm_has_sync_mmu ())) { -madvise (qemu_get_ram_ptr (addr),TARGET_PAGE_SIZE , -MADV_DONTNEED ); +madvise (host,TARGET_PAGE_SIZE ,MADV_DONTNEED ); }#endif }else if (flags &RAM_SAVE_FLAG_PAGE ) { -qemu_get_buffer (f,qemu_get_ram_ptr (addr),TARGET_PAGE_SIZE ); +void *host; + +if (version_id ==3 ) { + host =qemu_get_ram_ptr (addr); + }else { +RAMBlock *block; +char id[256 ]; +uint8_t len; + + len =qemu_get_byte (f); +qemu_get_buffer (f, (uint8_t *)id, len); + id[len] =0 ; + + QLIST_FOREACH(block, &ram_list.blocks, next) { +if (!strncmp (id, block->idstr,sizeof (id))) +break ; + } +if (!block) +return -EINVAL ; + + host = block->host + addr; + } +qemu_get_buffer (f, host,TARGET_PAGE_SIZE ); }if (qemu_file_has_error (f)) {return -EIO ;
vl.c +1 -1
@@ -2794,7 +2794,7 @@ int main(int argc, char **argv, char **envp)if (qemu_opts_foreach (&qemu_drive_opts, drive_init_func, &machine->use_scsi,1 ) !=0 )exit (1 ); -register_savevm_live (NULL ,"ram" ,0 ,3 ,NULL , ram_save_live,NULL , +register_savevm_live (NULL ,"ram" ,0 ,4 ,NULL , ram_save_live,NULL , ram_load,NULL );if (nb_numa_nodes >0 ) {
PATCH v3 12/16 savevm: Use RAM blocks for basis of migration
In reply to Alex WilliamsonWe don't want to assume a contiguous address space, so migrate based on RAM blocks instead of a fixed linear address map. This will allow us to have holes in the ram_addr_t namespace, so we can implement qemu_ram_free().
---
arch_init.c | 67 +++++++++++++++++++++++++++++++++++++---------------------- 1 files changed, 42 insertions(+), 25 deletions(-)
arch_init.c +42 -25
@@ -105,27 +105,26 @@ static int is_dup_page(uint8_t *page, uint8_t ch)static int ram_save_block (QEMUFile *f) { -static ram_addr_t current_addr =0 ; -ram_addr_t saved_addr = current_addr; -ram_addr_t addr =0 ; -uint64_t total_ram =ram_bytes_total (); +static RAMBlock *last_block =NULL ; +static ram_addr_t last_offset =0 ; +RAMBlock *block = last_block; +ram_addr_t offset = last_offset; +ram_addr_t current_addr;int bytes_sent =0 ; -while (addr < total_ram) { +if (!block) + block =QLIST_FIRST (&ram_list.blocks); + + current_addr = block->offset + offset; + + do { if (cpu_physical_memory_get_dirty(current_addr, MIGRATION_DIRTY_FLAG)) { -RAMBlock *block; -ram_addr_t offset; uint8_t *p; cpu_physical_memory_reset_dirty(current_addr, current_addr + TARGET_PAGE_SIZE, MIGRATION_DIRTY_FLAG); - QLIST_FOREACH(block, &ram_list.blocks, next) { -if (current_addr - block->offset < block->length) -break ; - } - offset = current_addr - block->offset; p = block->host + offset; if (is_dup_page(p, *p)) {
@@ -146,9 +145,21 @@ static int ram_save_block(QEMUFile *f)break ; } - addr +=TARGET_PAGE_SIZE ; - current_addr = (saved_addr + addr) % total_ram; - } + + offset +=TARGET_PAGE_SIZE ; +if (offset >= block->length) { + offset =0 ; + block =QLIST_NEXT (block, next); +if (!block) + block =QLIST_FIRST (&ram_list.blocks); + } + + current_addr = block->offset + offset; + + }while (current_addr != last_block->offset + last_offset); + + last_block = block; + last_offset = offset;return bytes_sent; }
@@ -157,13 +168,16 @@ static uint64_t bytes_transferred;static ram_addr_t ram_save_remaining (void ) { -ram_addr_t addr; +RAMBlock *block;ram_addr_t count =0 ; -uint64_t total_ram =ram_bytes_total (); -for (addr =0 ; addr < total_ram; addr +=TARGET_PAGE_SIZE ) { -if (cpu_physical_memory_get_dirty (addr,MIGRATION_DIRTY_FLAG )) { - count++; + QLIST_FOREACH(block, &ram_list.blocks, next) { +ram_addr_t addr; +for (addr = block->offset; addr < block->offset + block->length; + addr +=TARGET_PAGE_SIZE ) { +if (cpu_physical_memory_get_dirty (addr,MIGRATION_DIRTY_FLAG )) { + count++; + } } }
@@ -210,20 +224,23 @@ int ram_save_live(Monitor *mon, QEMUFile *f, int stage, void *opaque)if (stage ==1 ) {RAMBlock *block; -uint64_t total_ram =ram_bytes_total (); bytes_transferred =0 ;/* Make sure all dirty bits are set */ -for (addr =0 ; addr < total_ram; addr +=TARGET_PAGE_SIZE ) { -if (!cpu_physical_memory_get_dirty (addr,MIGRATION_DIRTY_FLAG )) { -cpu_physical_memory_set_dirty (addr); + QLIST_FOREACH(block, &ram_list.blocks, next) { +for (addr = block->offset; addr < block->offset + block->length; + addr +=TARGET_PAGE_SIZE ) { +if (!cpu_physical_memory_get_dirty (addr, +MIGRATION_DIRTY_FLAG )) { +cpu_physical_memory_set_dirty (addr); + } } }/* Enable dirty memory tracking */ cpu_physical_memory_set_dirty_tracking (1 ); -qemu_put_be64 (f, total_ram |RAM_SAVE_FLAG_MEM_SIZE ); +qemu_put_be64 (f,ram_bytes_total () |RAM_SAVE_FLAG_MEM_SIZE ); QLIST_FOREACH(block, &ram_list.blocks, next) {qemu_put_byte (f,strlen (block->idstr));
PATCH v3 13/16 savevm: Create a new continue flag to avoid resending block name
In reply to Alex WilliamsonAllows us to compress the protocol a bit by setting a flag on the offset which indicates we're still working within the same block as last time. That way we can avoid sending the block name for every page. Suggested by Anthony Liguori.
---
arch_init.c | 94 +++++++++++++++++++++++++++++++---------------------------- 1 files changed, 50 insertions(+), 44 deletions(-)
arch_init.c +50 -44
@@ -87,6 +87,7 @@ const uint32_t arch_type = QEMU_ARCH;#define RAM_SAVE_FLAG_MEM_SIZE 0x04#define RAM_SAVE_FLAG_PAGE 0x08#define RAM_SAVE_FLAG_EOS 0x10 +#define RAM_SAVE_FLAG_CONTINUE 0x20static int is_dup_page (uint8_t *page,uint8_t ch) {
@@ -120,6 +121,7 @@ static int ram_save_block(QEMUFile *f) do { if (cpu_physical_memory_get_dirty(current_addr, MIGRATION_DIRTY_FLAG)) { uint8_t *p; + int cont = (block == last_block) ? RAM_SAVE_FLAG_CONTINUE : 0; cpu_physical_memory_reset_dirty(current_addr, current_addr + TARGET_PAGE_SIZE,
@@ -128,17 +130,21 @@ static int ram_save_block(QEMUFile *f) p = block->host + offset;if (is_dup_page (p, *p)) { -qemu_put_be64 (f, offset |RAM_SAVE_FLAG_COMPRESS ); -qemu_put_byte (f,strlen (block->idstr)); -qemu_put_buffer (f, (uint8_t *)block->idstr, -strlen (block->idstr)); +qemu_put_be64 (f, offset | cont |RAM_SAVE_FLAG_COMPRESS ); +if (!cont) { +qemu_put_byte (f,strlen (block->idstr)); +qemu_put_buffer (f, (uint8_t *)block->idstr, +strlen (block->idstr)); + }qemu_put_byte (f, *p); bytes_sent =1 ; }else { -qemu_put_be64 (f, offset |RAM_SAVE_FLAG_PAGE ); -qemu_put_byte (f,strlen (block->idstr)); -qemu_put_buffer (f, (uint8_t *)block->idstr, -strlen (block->idstr)); +qemu_put_be64 (f, offset | cont |RAM_SAVE_FLAG_PAGE ); +if (!cont) { +qemu_put_byte (f,strlen (block->idstr)); +qemu_put_buffer (f, (uint8_t *)block->idstr, +strlen (block->idstr)); + }qemu_put_buffer (f, p,TARGET_PAGE_SIZE ); bytes_sent =TARGET_PAGE_SIZE ; }
@@ -289,6 +295,36 @@ int ram_save_live(Monitor *mon, QEMUFile *f, int stage, void *opaque)return (stage ==2 ) && (expected_time <=migrate_max_downtime ()); } +static inline void *host_from_stream_offset (QEMUFile *f, +ram_addr_t offset, +int flags) +{ +static RAMBlock *block =NULL ; +char id[256 ]; +uint8_t len; + +if (flags &RAM_SAVE_FLAG_CONTINUE ) { +if (!block) { +fprintf (stderr,"Ack, bad migration stream!\n" ); +return NULL ; + } + +return block->host + offset; + } + + len =qemu_get_byte (f); +qemu_get_buffer (f, (uint8_t *)id, len); + id[len] =0 ; + + QLIST_FOREACH(block, &ram_list.blocks, next) { +if (!strncmp (id, block->idstr,sizeof (id))) +return block->host + offset; + } + +fprintf (stderr,"Can't find block %s!\n" , id); +return NULL ; +} +int ram_load (QEMUFile *f,void *opaque,int version_id) {ram_addr_t addr;
@@ -346,26 +382,11 @@ int ram_load(QEMUFile *f, void *opaque, int version_id)void *host;uint8_t ch; -if (version_id ==3 ) { +if (version_id ==3 ) host =qemu_get_ram_ptr (addr); - }else { -RAMBlock *block; -char id[256 ]; -uint8_t len; - - len =qemu_get_byte (f); -qemu_get_buffer (f, (uint8_t *)id, len); - id[len] =0 ; +else + host =host_from_stream_offset (f, addr, flags); - QLIST_FOREACH(block, &ram_list.blocks, next) { -if (!strncmp (id, block->idstr,sizeof (id))) -break ; - } -if (!block) -return -EINVAL ; - - host = block->host + addr; - } ch =qemu_get_byte (f);memset (host, ch,TARGET_PAGE_SIZE );#ifndef _WIN32
@@ -377,26 +398,11 @@ int ram_load(QEMUFile *f, void *opaque, int version_id) }else if (flags &RAM_SAVE_FLAG_PAGE ) {void *host; -if (version_id ==3 ) { +if (version_id ==3 ) host =qemu_get_ram_ptr (addr); - }else { -RAMBlock *block; -char id[256 ]; -uint8_t len; +else + host =host_from_stream_offset (f, addr, flags); - len =qemu_get_byte (f); -qemu_get_buffer (f, (uint8_t *)id, len); - id[len] =0 ; - - QLIST_FOREACH(block, &ram_list.blocks, next) { -if (!strncmp (id, block->idstr,sizeof (id))) -break ; - } -if (!block) -return -EINVAL ; - - host = block->host + addr; - }qemu_get_buffer (f, host,TARGET_PAGE_SIZE ); }if (qemu_file_has_error (f)) {
PATCH v3 14/16 qemu_ram_free: Implement it
In reply to Alex WilliamsonNow that we can support a ram_addr_t space with holes, we can implement qemu_ram_free().
---
cpu-all.h | 3 +++ exec.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 61 insertions(+), 4 deletions(-)
cpu-all.h +3 -0
@@ -867,6 +867,9 @@ typedef struct RAMBlock {ram_addr_t length;char idstr[256 ];QLIST_ENTRY (RAMBlock) next; +#if defined(__linux__) && !defined(TARGET_S390X ) +int fd; +#endif } RAMBlock; typedef struct RAMList {
exec.c +58 -4
98 lines not on this page · raw message
PATCH v3 15/16 pci: Free the space allocated for the option rom on removal
In reply to Alex Williamson---
hw/pci.c | 11 +++++++++++ 1 files changed, 11 insertions(+), 0 deletions(-)
hw/pci.c +11 -0
@@ -76,6 +76,7 @@ static struct BusInfo pci_bus_info = {static void pci_update_mappings (PCIDevice *d);static void pci_set_irq (void *opaque,int irq_num,int level);static int pci_add_option_rom (PCIDevice *pdev); +static void pci_del_option_rom (PCIDevice *pdev);static uint16_t pci_default_sub_vendor_id =PCI_SUBVENDOR_ID_REDHAT_QUMRANET ;static uint16_t pci_default_sub_device_id =PCI_SUBDEVICE_ID_QEMU ;
@@ -709,6 +710,7 @@ static int pci_unregister_device(DeviceState *dev)return ret;pci_unregister_io_regions (pci_dev); +pci_del_option_rom (pci_dev);do_pci_unregister_device (pci_dev);return 0 ; }
@@ -1765,6 +1767,15 @@ static int pci_add_option_rom(PCIDevice *pdev)return 0 ; } +static void pci_del_option_rom (PCIDevice *pdev) +{ +if (!pdev->rom_offset) +return ; + +qemu_ram_free (pdev->rom_offset); + pdev->rom_offset =0 ; +} +/* Reserve space and add capability to the linked list in pci config space */ int pci_add_capability_at_offset (PCIDevice *pdev,uint8_t cap_id,uint8_t offset,uint8_t size)
PATCH v3 16/16 ramblocks: No more being lazy about duplicate names
In reply to Alex WilliamsonNow that we have a working qemu_ram_free() and the primary runtime user of it has been updated, don't be lenient about duplicate id strings. We also shouldn't need to create them ondemand at the target.
---
arch_init.c | 5 +++-- exec.c | 13 +++---------- 2 files changed, 6 insertions(+), 12 deletions(-)
arch_init.c +3 -2
@@ -369,8 +369,9 @@ int ram_load(QEMUFile *f, void *opaque, int version_id) }if (!block) { -if (!qemu_ram_alloc (NULL , id, length)) -return -ENOMEM ; +fprintf (stderr,"Unknown ramblock \"%s\", cannot " +"accept migration\n" , id); +return -EINVAL ; } total_ram_bytes -= length;
exec.c +3 -10
@@ -2826,16 +2826,9 @@ ram_addr_t qemu_ram_alloc(DeviceState *dev, const char *name, ram_addr_t size) QLIST_FOREACH(block, &ram_list.blocks, next) {if (!strcmp (block->idstr, new_block->idstr)) { -if (block->length == new_block->length) { -fprintf (stderr,"RAMBlock \"%s\" exists, assuming lack of" -"free.\n" , new_block->idstr); -qemu_free (new_block); -return block->offset; - }else { -fprintf (stderr,"RAMBlock \"%s\" already registered with" -"different size, abort\n" , new_block->idstr); -abort (); - } +fprintf (stderr,"RAMBlock \"%s\" already registered, abort!\n" , + new_block->idstr); +abort (); } }