shadow: track video RAM dirty bits
PATCH 1/1 shadow: track video RAM dirty bits
Hello,
Here is a patch that adds to the shadow code support for tracking the dirty bits of video RAM. I will work on additional heuristics (like taking the time to write-protect the pages when dirty scan has been reporting no change for some time), but I'd like to see this tested first, and it's already quite useful: idle qemu CPU usage typically goes down from 3-6% to 0.2-0.4%.
shadow: track video RAM dirty bits
This adds a new HVM op that enables tracking dirty bits of a range of video RAM. The idea is to optimize just for the most common case (only one guest mapping, with sometimes some temporary other mappings), which permits to keep the overhead on shadow as low as possible.
tools/ioemu/hw/cirrus_vga.c +23 -25
@@ -234,8 +234,6 @@ typedef struct CirrusVGAState {int cirrus_linear_io_addr;int cirrus_linear_bitblt_io_addr;int cirrus_mmio_io_addr; -unsigned long cirrus_lfb_addr; -unsigned long cirrus_lfb_end;uint32_t cirrus_addr_mask;uint32_t linear_mmio_mask;uint8_t cirrus_shadow_gr0;
@@ -2657,11 +2655,11 @@ static void cirrus_update_memory_access( mode = s->gr[0x05 ] &0x7 ;if (mode <4 || mode >5 || ((s->gr[0x0B ] &0x4 ) ==0 )) { -if (s->cirrus_lfb_addr && s->cirrus_lfb_end && !s->map_addr) { +if (s->lfb_addr && s->lfb_end && !s->map_addr) {void *vram_pointer, *old_vram; - vram_pointer =set_vram_mapping (s->cirrus_lfb_addr, - s->cirrus_lfb_end); + vram_pointer =set_vram_mapping (s->lfb_addr, + s->lfb_end);if (!vram_pointer)fprintf (stderr,"NULL vram_pointer\n" );else {
@@ -2669,21 +2667,21 @@ static void cirrus_update_memory_access( VGA_RAM_SIZE);qemu_free (old_vram); } - s->map_addr = s->cirrus_lfb_addr; - s->map_end = s->cirrus_lfb_end; + s->map_addr = s->lfb_addr; + s->map_end = s->lfb_end; } s->cirrus_linear_write[0 ] = cirrus_linear_mem_writeb; s->cirrus_linear_write[1 ] = cirrus_linear_mem_writew; s->cirrus_linear_write[2 ] = cirrus_linear_mem_writel; }else { generic_io: -if (s->cirrus_lfb_addr && s->cirrus_lfb_end && s->map_addr) { +if (s->lfb_addr && s->lfb_end && s->map_addr) {void *old_vram; old_vram =vga_update_vram ((VGAState *)s,NULL ,VGA_RAM_SIZE ); -unset_vram_mapping (s->cirrus_lfb_addr, - s->cirrus_lfb_end, +unset_vram_mapping (s->lfb_addr, + s->lfb_end, old_vram); s->map_addr = s->map_end =0 ;
@@ -3049,27 +3047,27 @@ void cirrus_stop_acc(CirrusVGAState *s)if (s->map_addr){int error; s->map_addr =0 ; - error =unset_vram_mapping (s->cirrus_lfb_addr, - s->cirrus_lfb_end, s->vram_ptr); + error =unset_vram_mapping (s->lfb_addr, + s->lfb_end, s->vram_ptr);fprintf (stderr,"cirrus_stop_acc:unset_vram_mapping.\n" ); } }void cirrus_restart_acc (CirrusVGAState *s) { -if (s->cirrus_lfb_addr && s->cirrus_lfb_end) { +if (s->lfb_addr && s->lfb_end) {void *vram_pointer, *old_vram;fprintf (stderr,"cirrus_vga_load:re-enable vga acc.lfb_addr=0x%lx, lfb_end=0x%lx.\n" , - s->cirrus_lfb_addr, s->cirrus_lfb_end); - vram_pointer =set_vram_mapping (s->cirrus_lfb_addr ,s->cirrus_lfb_end); + s->lfb_addr, s->lfb_end); + vram_pointer =set_vram_mapping (s->lfb_addr ,s->lfb_end);if (!vram_pointer){fprintf (stderr,"cirrus_vga_load:NULL vram_pointer\n" ); }else { old_vram =vga_update_vram ((VGAState *)s, vram_pointer,VGA_RAM_SIZE );qemu_free (old_vram); - s->map_addr = s->cirrus_lfb_addr; - s->map_end = s->cirrus_lfb_end; + s->map_addr = s->lfb_addr; + s->map_end = s->lfb_end; } } }
@@ -3120,8 +3118,8 @@ static void cirrus_vga_save(QEMUFile *f, vga_acc = (!!s->map_addr);qemu_put_8s (f, &vga_acc); -qemu_put_be64s (f, (uint64_t *)&s->cirrus_lfb_addr); -qemu_put_be64s (f, (uint64_t *)&s->cirrus_lfb_end); +qemu_put_be64s (f, (uint64_t *)&s->lfb_addr); +qemu_put_be64s (f, (uint64_t *)&s->lfb_end);qemu_put_buffer (f, s->vram_ptr,VGA_RAM_SIZE ); }
@@ -3175,8 +3173,8 @@ static int cirrus_vga_load(QEMUFile *f,qemu_get_be32s (f, &s->hw_cursor_y);qemu_get_8s (f, &vga_acc); -qemu_get_be64s (f, (uint64_t *)&s->cirrus_lfb_addr); -qemu_get_be64s (f, (uint64_t *)&s->cirrus_lfb_end); +qemu_get_be64s (f, (uint64_t *)&s->lfb_addr); +qemu_get_be64s (f, (uint64_t *)&s->lfb_end);qemu_get_buffer (f, s->vram_ptr,VGA_RAM_SIZE );if (vga_acc){cirrus_restart_acc (s);
@@ -3337,11 +3335,11 @@ static void cirrus_pci_lfb_map(PCIDevice/* XXX: add byte swapping apertures */ cpu_register_physical_memory (addr, s->vram_size, s->cirrus_linear_io_addr); - s->cirrus_lfb_addr = addr; - s->cirrus_lfb_end = addr +VGA_RAM_SIZE ; + s->lfb_addr = addr; + s->lfb_end = addr +VGA_RAM_SIZE ; -if (s->map_addr && (s->cirrus_lfb_addr != s->map_addr) && - (s->cirrus_lfb_end != s->map_end)) +if (s->map_addr && (s->lfb_addr != s->map_addr) && + (s->lfb_end != s->map_end))fprintf (logfile,"cirrus vga map change while on lfb mode\n" ); cpu_register_physical_memory(addr + 0x1000000, 0x400000,
tools/ioemu/hw/vga.c +79 -4
144 lines not on this page · raw message
tools/ioemu/hw/vga_int.h +2 -0
@@ -87,6 +87,8 @@unsigned int vram_size; \unsigned long bios_offset; \unsigned int bios_size; \ +unsigned long lfb_addr; \ +unsigned long lfb_end; \PCIDevice *pci_dev; \uint32_t latch; \uint8_t sr_index; \
tools/libxc/xc_misc.c +31 -0
@@ -236,6 +236,37 @@ int xc_hvm_set_pci_link_route(return rc; } +int xc_hvm_track_dirty_vram ( +int xc_handle,domid_t dom, +uint64_t first_pfn,uint64_t nr, +unsigned long *dirty_bitmap) +{ +DECLARE_HYPERCALL ; +struct xen_hvm_track_dirty_vram arg; +int rc; + + hypercall.op = __HYPERVISOR_hvm_op; + hypercall.arg[0 ] = HVMOP_track_dirty_vram; + hypercall.arg[1 ] = (unsigned long )&arg; + + arg.domid = dom; + arg.first_pfn = first_pfn; + arg.nr = nr; +set_xen_guest_handle (arg.dirty_bitmap, (uint8_t *)dirty_bitmap); + +if ( (rc =lock_pages (&arg,sizeof (arg))) !=0 ) + { +PERROR ("Could not lock memory" ); +return rc; + } + + rc =do_xen_hypercall (xc_handle, &hypercall); + +unlock_pages (&arg,sizeof (arg)); + +return rc; +} +void *xc_map_foreign_pages (int xc_handle,uint32_t dom,int prot,const xen_pfn_t *arr,int num) {
tools/libxc/xenctrl.h +16 -0
@@ -862,6 +862,22 @@ int xc_hvm_set_pci_link_route( int xc_handle, domid_t dom, uint8_t link, uint8_t isa_irq); +/* +* Track dirty bit changes in the VRAM area +* +* All of this is done atomically: +* - get the dirty bitmap since the last call +* - set up dirty tracking area for period up to the next call +* - clear the dirty tracking area. +* +* Returns -ENODATA and does not fill bitmap if the area has changed since the +* last call. +*/ +int xc_hvm_track_dirty_vram ( +int xc_handle,domid_t dom, +uint64_t first_pfn,uint64_t nr, +unsigned long *bitmap); + typedef enum { XC_ERROR_NONE = 0, XC_INTERNAL_ERROR = 1,
xen/arch/ia64/vmx/vmx_hypercall.c +4 -0
@@ -200,6 +200,10 @@ do_hvm_op(unsigned long op, XEN_GUEST_HA rc =0 ;break ; +case HVMOP_track_dirty_vram: + rc = -ENOSYS ; +break ; +default :gdprintk (XENLOG_INFO ,"Bad HVM op %ld.\n" , op); rc = -ENOSYS ;
xen/arch/x86/hvm/hvm.c +48 -0
54 lines not on this page · raw message
xen/arch/x86/mm/shadow/common.c +165 -0
177 lines not on this page · raw message
xen/arch/x86/mm/shadow/multi.c +81 -2
114 lines not on this page · raw message
xen/arch/x86/mm/shadow/private.h +9 -0
@@ -528,6 +528,15 @@ sh_mfn_is_dirty(struct domain *d, mfn_treturn rv; } +/**************************************************************************/ +/* VRAM dirty tracking support */ + +struct sh_dirty_vram { +unsigned long begin_pfn; +unsigned long end_pfn; +paddr_t *sl1ma; +uint8_t *dirty_bitmap; +};/**************************************************************************/ /* Shadow-page refcounting. */
xen/include/asm-ia64/config.h +1 -0
@@ -71,6 +71,7 @@ typedef int pid_t;// now needed for xen/include/mm.h typedef unsigned long paddr_t ; +#define INVALID_PADDR (~0UL)// from include/linux/kernel.h #define ALIGN (x,a) (((x)+(a)-1)&~((a)-1))
xen/include/asm-powerpc/types.h +1 -0
@@ -61,6 +61,7 @@ typedef unsigned long size_t;#endif typedef unsigned long paddr_t ; +#define INVALID_PADDR (~0UL)#define PRIpaddr "08lx"/* DMA addresses come in generic and 64-bit flavours. */
xen/include/asm-x86/shadow.h +6 -0
@@ -62,6 +62,12 @@ void shadow_vcpu_init(struct vcpu *v);/* Enable an arbitrary shadow mode. Call once at domain creation. */ int shadow_enable (struct domain *d,u32 mode); +/* Enable VRAM dirty bit tracking. */ +int shadow_track_dirty_vram (struct domain *d, +unsigned long first_pfn, +unsigned long nr, +XEN_GUEST_HANDLE_64 (uint8) dirty_bitmap); +/* Handler for shadow control ops: operations from user-space to enable * and disable ephemeral shadow modes (test mode and log-dirty mode) and * manipulate the log-dirty bitmap. */
xen/include/asm-x86/types.h +3 -0
@@ -38,15 +38,18 @@ typedef unsigned long long u64;typedef unsigned long long u64 ;#if defined(CONFIG_X86_PAE )typedef u64 paddr_t ; +#define INVALID_PADDR (~0ULL)#define PRIpaddr "016llx"#else typedef unsigned long paddr_t ; +#define INVALID_PADDR (~0UL)#define PRIpaddr "08lx"#endif #elif defined(__x86_64__)typedef signed long s64 ;typedef unsigned long u64 ;typedef unsigned long paddr_t ; +#define INVALID_PADDR (~0UL)#define PRIpaddr "016lx"#endif
xen/include/public/hvm/hvm_op.h +16 -0
@@ -73,4 +73,20 @@ DEFINE_XEN_GUEST_HANDLE(xen_hvm_set_pci_/* Flushes all VCPU TLBs: @arg must be NULL. */ #define HVMOP_flush_tlbs 5 +/* Track dirty VRAM. */ +#define HVMOP_track_dirty_vram 6 +struct xen_hvm_track_dirty_vram { +/* Domain to be tracked. */ +domid_t domid; +/* First pfn to track. */ +uint64_aligned_t first_pfn; +/* Number of pages to track. */ +uint64_aligned_t nr; +/* OUT variable. */ +/* Dirty bitmap buffer. */ +XEN_GUEST_HANDLE_64 (uint8) dirty_bitmap; +}; +typedef struct xen_hvm_track_dirty_vram xen_hvm_track_dirty_vram_t ; +DEFINE_XEN_GUEST_HANDLE (xen_hvm_track_dirty_vram_t); +#endif /* __XEN_PUBLIC_HVM_HVM_OP_H__ */
xen/include/xen/sched.h +3 -0
@@ -236,6 +236,9 @@ struct domain* cause a deadlock. Acquirers don't spin waiting; they preempt. */ spinlock_t hypercall_deadlock_mutex; + +/* VRAM dirty support. */ +struct sh_dirty_vram *dirty_vram; };struct domain_setup_info
shadow: Fix dirty vram tracking typo.
xen/arch/x86/mm/shadow/multi.c +1 -1
@@ -1321,7 +1321,7 @@ static inline void shadow_vram_put_l1e(s } }if ( dirty ) - d->dirty_vram->dirty_bitmap[i /8 ] |= d->dirty_vram->dirty_bitmap[i %8 ]; + d->dirty_vram->dirty_bitmap[i /8 ] |=1 << (i %8 ); } }
PATCH 1/1 shadow: drop guest VRAM write access after some idleness
In reply to Samuel Thibaultshadow: drop guest VRAM write access after some idleness
If the video RAM has been kept clean for at least 2 seconds, we can afford taking the time to drop guest write access, which allows us to save the dirty bit scanning entirely until we get a guest page handle.
xen/arch/x86/mm/shadow/common.c +40 -13
@@ -2871,6 +2871,8 @@ int shadow_track_dirty_vram(struct domaiunsigned long end_pfn = begin_pfn + nr;unsigned long dirty_size = (nr +7 ) /8 ;int flush_tlb =0 ; +unsigned long i; +p2m_type_t t;if (end_pfn < begin_pfn || begin_pfn > d->arch.p2m->max_mapped_pfn
@@ -2881,7 +2883,8 @@ int shadow_track_dirty_vram(struct domaiif ( d->dirty_vram && (!nr || ( begin_pfn != d->dirty_vram->begin_pfn - || end_pfn != d->dirty_vram->end_pfn )) ) { + || end_pfn != d->dirty_vram->end_pfn )) ) + {/* Different tracking, tear the previous down. */ gdprintk (XENLOG_INFO ,"stopping tracking VRAM %lx - %lx\n" , d->dirty_vram->begin_pfn, d->dirty_vram->end_pfn);xfree (d->dirty_vram->sl1ma);
@@ -2890,17 +2893,16 @@ int shadow_track_dirty_vram(struct domai d->dirty_vram =NULL ; } -if ( !nr ) { +if ( !nr ) + { rc =0 ; goto out; }/* This should happen seldomly (Video mode change), * no need to be careful. */ -if ( !d->dirty_vram ) { -unsigned long i; -p2m_type_t t; - +if ( !d->dirty_vram ) + {/* Just recount from start. */ for ( i = begin_pfn; i < end_pfn; i++ ) flush_tlb |=sh_remove_all_mappings (d->vcpu[0 ],gfn_to_mfn (d, i, &t));
@@ -2921,10 +2923,20 @@ int shadow_track_dirty_vram(struct domai goto out_sl1ma;memset (d->dirty_vram->dirty_bitmap,0 , dirty_size); + d->dirty_vram->last_dirty =NOW (); +/* Tell the caller that this time we could not track dirty bits. */ rc = -ENODATA ; - }else { -int i; + } +else if (d->dirty_vram->last_dirty ==-1 ) + { +/* still completely clean, just copy our empty bitmap */ + rc = -EFAULT ; +if (copy_to_guest (dirty_bitmap, d->dirty_vram->dirty_bitmap, dirty_size) ==0 ) + rc =0 ; + } +else + {#ifdef __i386__unsigned long map_mfn =INVALID_MFN ;void *map_sl1p =NULL ;
@@ -2932,26 +2944,29 @@ int shadow_track_dirty_vram(struct domai/* Iterate over VRAM to track dirty bits. */ for ( i =0 ; i < nr; i++ ) { -p2m_type_t t;mfn_t mfn =gfn_to_mfn (d, begin_pfn + i, &t);struct page_info *page =mfn_to_page (mfn);u32 count_info = page->u.inuse.type_info & PGT_count_mask;int dirty =0 ;paddr_t sl1ma = d->dirty_vram->sl1ma[i]; -switch (count_info) { +switch (count_info) + {case 0 :/* No guest reference, nothing to track. */ break ;case 1 :/* One guest reference. */ -if ( sl1ma ==INVALID_PADDR ) { +if ( sl1ma ==INVALID_PADDR ) + {/* We don't know which sl1e points to this, too bad. */ dirty =1 ;/* TODO: Heuristics for finding the single mapping of * this gmfn */ flush_tlb |=sh_remove_all_mappings (d->vcpu[0 ],gfn_to_mfn (d, begin_pfn + i, &t)); - }else { + } +else + {/* Hopefully the most common case: only one mapping, * whose dirty bit we can use. */ l1_pgentry_t *sl1e;
@@ -2970,7 +2985,8 @@ int shadow_track_dirty_vram(struct domai sl1e =maddr_to_virt (sl1ma);#endif -if (l1e_get_flags (*sl1e) & _PAGE_DIRTY ) { +if (l1e_get_flags (*sl1e) & _PAGE_DIRTY ) + { dirty =1 ;/* Note: this is atomic, so we may clear a * _PAGE_ACCESSED set by another processor. */
@@ -2987,7 +3003,10 @@ int shadow_track_dirty_vram(struct domai }if ( dirty ) + { d->dirty_vram->dirty_bitmap[i /8 ] |=1 << (i %8 ); + d->dirty_vram->last_dirty =NOW (); + } }#ifdef __i386__
@@ -2998,6 +3017,14 @@ int shadow_track_dirty_vram(struct domai rc = -EFAULT ;if (copy_to_guest (dirty_bitmap, d->dirty_vram->dirty_bitmap, dirty_size) ==0 ) {memset (d->dirty_vram->dirty_bitmap,0 , dirty_size); +if (d->dirty_vram->last_dirty +SECONDS (2 ) <NOW ()) + { +/* was clean for more than two seconds, try to disable guest +* write access */ +for ( i = begin_pfn; i < end_pfn; i++ ) + flush_tlb |=sh_remove_write_access (d->vcpu[0 ],gfn_to_mfn (d, i, &t),1 ,0 ); + d->dirty_vram->last_dirty =-1 ; + } rc =0 ; } }
xen/arch/x86/mm/shadow/multi.c +14 -1
@@ -870,6 +870,17 @@ _sh_propagate(struct vcpu *v, } } +if (unlikely ((level ==1 ) && d->dirty_vram + && d->dirty_vram->last_dirty ==-1 + &&gfn_x (target_gfn) >= d->dirty_vram->begin_pfn + &&gfn_x (target_gfn) < d->dirty_vram->end_pfn) ) + { +if ( ft &FETCH_TYPE_WRITE ) + d->dirty_vram->last_dirty =NOW (); +else + sflags &= ~_PAGE_RW; + } +/* Read-only memory */ if ( p2mt == p2m_ram_ro ) sflags &= ~_PAGE_RW;
@@ -1320,8 +1331,10 @@ static inline void shadow_vram_put_l1e(s* just hope it will remain. */ } } -if ( dirty ) +if ( dirty ) { d->dirty_vram->dirty_bitmap[i /8 ] |=1 << (i %8 ); + d->dirty_vram->last_dirty =NOW (); + } } }
xen/arch/x86/mm/shadow/private.h +1 -0
@@ -536,6 +536,7 @@ struct sh_dirty_vram {unsigned long end_pfn;paddr_t *sl1ma;uint8_t *dirty_bitmap; +s_time_t last_dirty; };/**************************************************************************/