gnttab: Improve scaleability
v12 cover gnttab: Improve scaleability
The series builds on the original series by Matt Wilson and Christoph Egger from Amazon.
Performance results for aggregate intrahost network throughput (between 20 VM pairs, with 16 dom0 VCPUs) show substantial improvements.
Throughput/Gbit/s
Base 9.7
Split locks 25.8
Split locks 42.9
+ per-VCPU maptrack lists
v12:
* Check for same refs in __gnttab_swap_grant_ref() to avoid deadlock.
* Assert local grant table lock is held in mapcount().
* Document that maptrack_lock is only for the free list.
* Document locking needed for maptrack entries (the active entry
lock).
* Try and steal maptrack entries if a VCPU runs out (RFC).
v11:
* Fix exists test in gnttab_map_exists().
* Call gnttab_need_iommu_mapping() once for each lock/unlock.
* Take active entry lock in gnttab_transfer().
* Use read_atomic() when checking maptrack flags for validity (note:
all other reads of map->flags are either after it is validated as
stable or protected by the active entry lock and thus don't need
read_atomic()). * Add comment to double_gt_lock().
* Allocate maptrack array with vzalloc() since it may be >1 page.
v10:
* Reduce scope of act in grant_map_exists().
* Make unlock sequence in error paths consistent in
__acquire_grant_for_copy().
* gnt_unlock_out -> to gt_unlock_out in __acquire_grant_for_copy().
* In gnttab_map_grant_ref(), keep double lock around maptrack update if gnttab_need_iommu_mapping(). Use a wmb(), otherwise.
* Divide max_maptrack_frames evenly amongst the VCPUs.
* Increase default max_maptrack_frames to compensate.
v9:
* Refactor the locking patches into three commits:
1. Add per-active entry locking.
2. Add maptrack_lock.
3. Make the grant table lock a read-write lock.
* Keep the double lock around IOMMU updates, but adjust their scope to only the IOMMU updates.
v8:
* Misc locking fixes.
* Use read/write_atomic() in per-VCPU maptrack free list.
* Improve locking docs.
v7:
* Re-add grant table rwlock (locking was broken without it and the gains are minimal anyway).
* Remove unneeded locks from grant_table_destroy().
* Fix get_maptrack_handle() locking.
v6:
* Remove most uses of the grant table lock.
* Make the grant table lock a spin lock again (there were only writers left after the above)
* Add per-VCPU maptrack free lists.
v5:
* Addressed locking issue pointed out by Jan Beulich
* Fixed git rebase merge issue introduced in v4 (acquiring locking twice)
* Change for ()-loop in grant_map_exists
* Coding style fixes
v4:
* Coding style nits from Jan Beulich
* Fixup read locks pointed out by Jan Beulich
* renamed double_gt_(un)lock to double_maptrack_(un)lock per request from Jan Beulich
* Addressed ASSERT()'s from Jan Beulich
* Addressed locking issues in unmap_common pointed out by Jan Beulich
v3:
* Addressed gnttab_swap_grant_ref() comment from Andrew Cooper
v2:
* Add arm part per request from Julien Grall
David
PATCH v12 1/5 gnttab: per-active entry locking
Introduce a per-active entry spin lock to protect active entry state The grant table lock must be locked before acquiring (locking) an active entry.
This is a step in reducing contention on the grant table lock, but will only do so once the grant table lock is turned into a read-write lock.
Based on a patch originally by Matt Wilson <[email protected]>.
---
v12:
- Check for same refs in __gnttab_swap_grant_ref() to avoid deadlock.
- Assert local grant table lock is held in mapcount().
v11:
- Fix exists test in gnttab_map_exists().
v10:
- Reduce scope of act in grant_map_exists().
- Make unlock sequence in error paths consistent in __acquire_grant_for_copy().
- gnt_unlock_out -> to gt_unlock_out in __acquire_grant_for_copy().
--- docs/misc/grant-tables.txt | 42 +++++++++++- xen/common/grant_table.c | 155 ++++++++++++++++++++++++++++++++------------ 2 files changed, 156 insertions(+), 41 deletions(-)
docs/misc/grant-tables.txt +41 -1
@@ -63,6 +63,7 @@ is complete. act->domid : remote domain being granted rights act->frame : machine frame being granted act->pin : used to hold reference counts + act->lock : spinlock used to serialize access to active entry state Map tracking ~~~~~~~~~~~~
@@ -74,7 +75,46 @@ is complete. matching map track entry is then removed, as if unmap had been invoked. These are not used by the transfer mechanism. map->domid : owner of the mapped frame - map->ref_and_flags : grant reference, ro/rw, mapped for host or device access + map->ref : grant reference + map->flags : ro/rw, mapped for host or device access + +******************************************************************************** + Locking + ~~~~~~~ + Xen uses several locks to serialize access to the internal grant table state. + + grant_table->lock : lock used to prevent readers from accessing + inconsistent grant table state such as current + version, partially initialized active table pages, + etc. + active_grant_entry->lock : spinlock used to serialize modifications to + active entries + + The primary lock for the grant table is a spinlock. All functions + that access members of struct grant_table must acquire the lock + around critical sections. + + Active entries are obtained by calling active_entry_acquire(gt, ref). + This function returns a pointer to the active entry after locking its + spinlock. The caller must hold the grant table lock for the gt in + question before calling active_entry_acquire(). This is because the + grant table can be dynamically extended via gnttab_grow_table() while + a domain is running and must be fully initialized. Once all access to + the active entry is complete, release the lock by calling + active_entry_release(act). + + Summary of rules for locking: + active_entry_acquire() and active_entry_release() can only be + called when holding the relevant grant table's lock. I.e.: + spin_lock(>->lock); + act = active_entry_acquire(gt, ref); + ... + active_entry_release(act); + spin_unlock(>->lock); + + Active entries cannot be acquired while holding the maptrack lock. + Multiple active entries can be acquired while holding the grant table + lock. ********************************************************************************
xen/common/grant_table.c +115 -40
431 lines not on this page · raw message
Signature · 1 line
1.7.10.4
Re: [PATCHv12 1/5] gnttab: per-active entry locking
Quoted 9 lines
Quoted 1 line
Quoted 1 line · On 12.06.15 at 15:33, <[email protected]> wrote:
Introduce a per-active entry spin lock to protect active entry state The grant table lock must be locked before acquiring (locking) an active entry.
This is a step in reducing contention on the grant table lock, but will only do so once the grant table lock is turned into a read-write lock.
Quoted 6 lines
Based on a patch originally by Matt Wilson <[email protected]>.
Signed-off-by David Vrabel <[email protected]>---
v12:
- Check for same refs in__gnttab_swap_grant_ref()to avoid deadlock.
I'm glad you spotted this, because the check having been there until v8 I didn't notice it got lost later on.
Jan
PATCH v12 2/5 gnttab: introduce maptrack lock
In reply to David VrabelSplit grant table lock into two separate locks. One to protect maptrack free list (maptrack_lock) and one for everything else (lock).
Based on a patch originally by Matt Wilson <[email protected]>.
---
v12:
- Document that maptrack_lock is only for the free list.
--- docs/misc/grant-tables.txt | 4 ++++ xen/common/grant_table.c | 9 +++++---- xen/include/xen/grant_table.h | 2 ++ 3 files changed, 11 insertions(+), 4 deletions(-)
docs/misc/grant-tables.txt +4 -0
@@ -87,6 +87,7 @@ is complete. inconsistent grant table state such as current version, partially initialized active table pages, etc. + grant_table->maptrack_lock : spinlock used to protect the maptrack free list active_grant_entry->lock : spinlock used to serialize modifications to active entries
@@ -94,6 +95,9 @@ is complete. that access members of struct grant_table must acquire the lock around critical sections. + The maptrack free list is protected by its own spinlock. The maptrack + lock may be locked while holding the grant table lock. + Active entries are obtained by calling active_entry_acquire(gt, ref). This function returns a pointer to the active entry after locking its spinlock. The caller must hold the grant table lock for the gt in
xen/common/grant_table.c +5 -4
@@ -288,10 +288,10 @@ static inline void put_maptrack_handle( struct grant_table *t, int handle) { -spin_lock (&t->lock); +spin_lock (&t->maptrack_lock);maptrack_entry (t, handle).ref = t->maptrack_head; t->maptrack_head = handle; -spin_unlock (&t->lock); +spin_unlock (&t->maptrack_lock); } static inline int
@@ -303,7 +303,7 @@ get_maptrack_handle(struct grant_mapping *new_mt;unsigned int new_mt_limit, nr_frames; -spin_lock (&lgt->lock); +spin_lock (&lgt->maptrack_lock);while (unlikely ((handle =__get_maptrack_handle (lgt)) ==-1 ) ) {
@@ -332,7 +332,7 @@ get_maptrack_handle( nr_frames + 1); } -spin_unlock (&lgt->lock); +spin_unlock (&lgt->maptrack_lock);return handle; }
@@ -3017,6 +3017,7 @@ grant_table_create(/* Simple stuff. */ spin_lock_init (&t->lock); +spin_lock_init (&t->maptrack_lock); t->nr_grant_frames =INITIAL_NR_GRANT_FRAMES ;/* Active grant table. */
xen/include/xen/grant_table.h +2 -0
@@ -82,6 +82,8 @@ struct grant_table {struct grant_mapping **maptrack;unsigned int maptrack_head;unsigned int maptrack_limit; +/* Lock protecting the maptrack page list, head, and limit */ +spinlock_t maptrack_lock;/* Lock protecting updates to active and shared grant tables. */ spinlock_t lock;/* The defined versions are 1 and 2. Set to 0 if we don't know
Signature · 1 line
1.7.10.4
PATCH v12 3/5 gnttab: make the grant table lock a read-write lock
In reply to David VrabelIn combination with the per-active entry locks, the grant table lock can be made a read-write lock since the majority of cases only the read lock is required. The grant table read lock protects against changes to the table version or size (which are done with the write lock held).
The write lock is also required when two active entries must be acquired.
The double lock is still required when updating IOMMU page tables.
With the lock contention being only on the maptrack lock (unless IOMMU updates are required), performance and scalability is improved.
Based on a patch originally by Matt Wilson <[email protected]>.
---
v12:
- Document locking needed for maptrack entries (the active entry lock).
v11:
- Call gnttab_need_iommu_mapping() once for each lock/unlock.
- Take active entry lock in gnttab_transfer().
- Use read_atomic() when checking maptrack flags for validity (note:
all other reads of map->flags are either after it is validated as stable or protected by the active entry lock and thus don't need read_atomic()).
- Add comment to double_gt_lock().
v10:
- In gnttab_map_grant_ref(), keep double lock around maptrack update if gnttab_need_iommu_mapping(). Use a wmb(), otherwise.
--- docs/misc/grant-tables.txt | 36 ++++++--- xen/arch/arm/mm.c | 4 +- xen/arch/x86/mm.c | 4 +- xen/common/grant_table.c | 177 ++++++++++++++++++++++++----------------- xen/include/xen/grant_table.h | 9 ++- 5 files changed, 135 insertions(+), 95 deletions(-)
docs/misc/grant-tables.txt +23 -13
@@ -83,7 +83,7 @@ is complete. ~~~~~~~ Xen uses several locks to serialize access to the internal grant table state. - grant_table->lock : lock used to prevent readers from accessing + grant_table->lock : rwlock used to prevent readers from accessing inconsistent grant table state such as current version, partially initialized active table pages, etc.
@@ -91,34 +91,44 @@ is complete. active_grant_entry->lock : spinlock used to serialize modifications to active entries - The primary lock for the grant table is a spinlock. All functions - that access members of struct grant_table must acquire the lock - around critical sections. + The primary lock for the grant table is a read/write spinlock. All + functions that access members of struct grant_table must acquire a + read lock around critical sections. Any modification to the members + of struct grant_table (e.g., nr_status_frames, nr_grant_frames, + active frames, etc.) must only be made if the write lock is + held. These elements are read-mostly, and read critical sections can + be large, which makes a rwlock a good choice. The maptrack free list is protected by its own spinlock. The maptrack lock may be locked while holding the grant table lock. Active entries are obtained by calling active_entry_acquire(gt, ref). This function returns a pointer to the active entry after locking its - spinlock. The caller must hold the grant table lock for the gt in - question before calling active_entry_acquire(). This is because the - grant table can be dynamically extended via gnttab_grow_table() while - a domain is running and must be fully initialized. Once all access to - the active entry is complete, release the lock by calling + spinlock. The caller must hold the grant table read lock before + calling active_entry_acquire(). This is because the grant table can + be dynamically extended via gnttab_grow_table() while a domain is + running and must be fully initialized. Once all access to the active + entry is complete, release the lock by calling active_entry_release(act). Summary of rules for locking: active_entry_acquire() and active_entry_release() can only be - called when holding the relevant grant table's lock. I.e.: - spin_lock(>->lock); + called when holding the relevant grant table's read lock. I.e.: + read_lock(>->lock); act = active_entry_acquire(gt, ref); ... active_entry_release(act); - spin_unlock(>->lock); + read_unlock(>->lock); Active entries cannot be acquired while holding the maptrack lock. Multiple active entries can be acquired while holding the grant table - lock. + _write_ lock. + + Maptrack entries are protected by the corresponding active entry + lock. As an exception, new maptrack entries may be populated without + holding the lock, provided the flags field is written last. This + requires any maptrack entry user validates the flags field as + non-zero first. ********************************************************************************
xen/arch/arm/mm.c +2 -2
@@ -1050,7 +1050,7 @@ int xenmem_add_to_physmap_one(switch ( space ) {case XENMAPSPACE_grant_table: -spin_lock (&d->grant_table->lock); +write_lock (&d->grant_table->lock);if ( d->grant_table->gt_version ==0 ) d->grant_table->gt_version =1 ;
@@ -1080,7 +1080,7 @@ int xenmem_add_to_physmap_one( t = p2m_ram_rw; -spin_unlock (&d->grant_table->lock); +write_unlock (&d->grant_table->lock);break ;case XENMAPSPACE_shared_info:if ( idx !=0 )
xen/arch/x86/mm.c +2 -2
@@ -4595,7 +4595,7 @@ int xenmem_add_to_physmap_one( mfn =virt_to_mfn (d->shared_info);break ;case XENMAPSPACE_grant_table: -spin_lock (&d->grant_table->lock); +write_lock (&d->grant_table->lock);if ( d->grant_table->gt_version ==0 ) d->grant_table->gt_version =1 ;
@@ -4617,7 +4617,7 @@ int xenmem_add_to_physmap_one( mfn =virt_to_mfn (d->grant_table->shared_raw[idx]); } -spin_unlock (&d->grant_table->lock); +write_unlock (&d->grant_table->lock);break ;case XENMAPSPACE_gmfn_range:case XENMAPSPACE_gmfn:
xen/common/grant_table.c +102 -75
521 lines not on this page · raw message
xen/include/xen/grant_table.h +6 -3
@@ -64,6 +64,11 @@ struct grant_mapping {/* Per-domain grant information. */ struct grant_table { +/* +* Lock protecting updates to grant table state (version, active +* entry list, etc.) +*/ +rwlock_t lock;/* Table size. Number of frames shared with guest */ unsigned int nr_grant_frames;/* Shared grant table (see include/public/grant_table.h). */
@@ -84,8 +89,6 @@ struct grant_table {unsigned int maptrack_limit;/* Lock protecting the maptrack page list, head, and limit */ spinlock_t maptrack_lock; -/* Lock protecting updates to active and shared grant tables. */ -spinlock_t lock;/* The defined versions are 1 and 2. Set to 0 if we don't know what version to use yet. */ unsigned gt_version;
@@ -103,7 +106,7 @@ gnttab_release_mappings( struct domain *d);/* Increase the size of a domain's grant table. -* Caller must hold d's grant table lock. +* Caller must hold d's grant table write lock. */ int gnttab_grow_table (struct domain *d,unsigned int req_nr_frames);
Signature · 1 line
1.7.10.4
PATCH v12 4/5 gnttab: use per-VCPU maptrack free lists
In reply to David VrabelFrom: Malcolm Crossley <[email protected]>
Performance analysis of aggregate network throughput with many VMs shows that performance is signficantly limited by contention on the maptrack lock when obtaining/releasing maptrack handles from the free list.
Instead of a single free list use a per-VCPU list. This avoids any contention when obtaining a handle. Handles must be released back to their original list and since this may occur on a different VCPU there is some contention on the destination VCPU's free list tail pointer (but this is much better than a per-domain lock).
Increase the default maximum number of maptrack frames by 4 times
because: a) struct grant_mapping is now 16 bytes (instead of 8); and
b) a guest may not evenly distribute all the grant map operations across the VCPUs (meaning some VCPUs need more maptrack entries than others).
---
v11:
- Allocate maptrack array with vzalloc() since it may be >1 page.
v10:
- Divide max_maptrack_frames evenly amongst the VCPUs.
- Increase default max_maptrack_frames to compensate.
--- xen/common/grant_table.c | 149 +++++++++++++++++++++++++---------------- xen/include/xen/grant_table.h | 8 ++- xen/include/xen/sched.h | 5 ++ 3 files changed, 101 insertions(+), 61 deletions(-)
xen/common/grant_table.c +91 -58
@@ -37,6 +37,7 @@#include <xen/iommu.h> #include <xen/paging.h> #include <xen/keyhandler.h> +#include <xen/vmap.h> #include <xsm/xsm.h> #include <asm/flushtlb.h>
@@ -57,7 +58,7 @@ integer_param("gnttab_max_frames", max_grant_frames);* New options allow to set max_maptrack_frames and * map_grant_table_frames independently. */ -#define DEFAULT_MAX_MAPTRACK_FRAMES 256 +#define DEFAULT_MAX_MAPTRACK_FRAMES 1024static unsigned int __read_mostly max_maptrack_frames;integer_param ("gnttab_max_maptrack_frames" , max_maptrack_frames);
@@ -117,12 +118,6 @@ struct gnttab_unmap_common {#define maptrack_entry (t, e) \ ((t)->maptrack[(e)/MAPTRACK_PER_PAGE][(e)%MAPTRACK_PER_PAGE]) -static inline unsigned int -nr_maptrack_frames (struct grant_table *t) -{ -return t->maptrack_limit /MAPTRACK_PER_PAGE ; -} -#define MAPTRACK_TAIL (~0u)#define SHGNT_PER_PAGE_V1 (PAGE_SIZE / sizeof(grant_entry_v1_t))
@@ -279,66 +274,104 @@ double_gt_unlock(struct grant_table *lgt, struct grant_table *rgt)static inline int __get_maptrack_handle ( -struct grant_table *t) +struct grant_table *t, +struct vcpu *v) { -unsigned int h; -if (unlikely ((h = t->maptrack_head) ==MAPTRACK_TAIL ) ) +unsigned int head, next; + +/* No maptrack pages allocated for this VCPU yet? */ + head = v->maptrack_head; +if (unlikely (head ==MAPTRACK_TAIL ) )return -1 ; - t->maptrack_head =maptrack_entry (t, h).ref; -return h; + +/* +* Always keep one entry in the free list to make it easier to add +* free entries to the tail. +*/ + next =read_atomic (&maptrack_entry (t, head).ref); +if (unlikely (next ==MAPTRACK_TAIL ) ) +return -1 ; + + v->maptrack_head = next; + +return head; }static inline void put_maptrack_handle (struct grant_table *t,int handle) { -spin_lock (&t->maptrack_lock); -maptrack_entry (t, handle).ref = t->maptrack_head; - t->maptrack_head = handle; -spin_unlock (&t->maptrack_lock); +struct domain *d = current->domain; +struct vcpu *v; +unsigned int prev_tail, cur_tail; + +/* 1. Set entry to be a tail. */ +maptrack_entry (t, handle).ref =MAPTRACK_TAIL ; + +/* 2. Add entry to the tail of the list on the original VCPU. */ + v = d->vcpu[maptrack_entry (t,handle).vcpu]; + + cur_tail =read_atomic (&v->maptrack_tail); +do { + prev_tail = cur_tail; + cur_tail =cmpxchg (&v->maptrack_tail, prev_tail, handle); + }while ( cur_tail != prev_tail ); + +/* 3. Update the old tail entry to point to the new entry. */ +write_atomic (&maptrack_entry (t, prev_tail).ref, handle); }static inline int get_maptrack_handle (struct grant_table *lgt) { +struct vcpu *v = current;int i;grant_handle_t handle;struct grant_mapping *new_mt; -unsigned int new_mt_limit, nr_frames; -spin_lock (&lgt->maptrack_lock); + handle =__get_maptrack_handle (lgt, v); +if (likely (handle !=-1 ) ) +return handle; -while (unlikely ((handle =__get_maptrack_handle (lgt)) ==-1 ) ) - { - nr_frames =nr_maptrack_frames (lgt); -if ( nr_frames >= max_maptrack_frames ) -break ; +/* +* max_maptrack_frames is per domain so each VCPU gets a share of +* the maximum, but allow at least one frame per VCPU. +*/ +if ( v->maptrack_frames + && v->maptrack_frames >= max_maptrack_frames / v->domain->max_vcpus ) +return -1 ; - new_mt =alloc_xenheap_page (); -if ( !new_mt ) -break ; + new_mt =alloc_xenheap_page (); +if ( !new_mt ) +return -1 ; +clear_page (new_mt); -clear_page (new_mt); +spin_lock (&lgt->maptrack_lock); - new_mt_limit = lgt->maptrack_limit +MAPTRACK_PER_PAGE ; +for ( i =1 ; i <MAPTRACK_PER_PAGE ; i++ ) + { + new_mt[i -1 ].ref = (lgt->maptrack_pages *MAPTRACK_PER_PAGE ) + i; + new_mt[i -1 ].vcpu = v->vcpu_id; + } +/* Set last entry vcpu and ref */ + new_mt[i -1 ].ref = v->maptrack_head; + new_mt[i -1 ].vcpu = v->vcpu_id; -for ( i =1 ; i <MAPTRACK_PER_PAGE ; i++ ) - new_mt[i -1 ].ref = lgt->maptrack_limit + i; - new_mt[i -1 ].ref = lgt->maptrack_head; - lgt->maptrack_head = lgt->maptrack_limit; + v->maptrack_head = lgt->maptrack_pages *MAPTRACK_PER_PAGE ; - lgt->maptrack[nr_frames] = new_mt; -smp_wmb (); - lgt->maptrack_limit = new_mt_limit; +/* Set tail directly if this is the first page for this VCPU. */ +if ( v->maptrack_tail ==MAPTRACK_TAIL ) + v->maptrack_tail = (lgt->maptrack_pages *MAPTRACK_PER_PAGE ) + +MAPTRACK_PER_PAGE -1 ; -gdprintk (XENLOG_INFO ,"Increased maptrack size to %u frames\n" , - nr_frames +1 ); - } + lgt->maptrack[lgt->maptrack_pages++] = new_mt;spin_unlock (&lgt->maptrack_lock); -return handle; + v->maptrack_frames++; + +return __get_maptrack_handle (lgt, v); }/* Number of grant table entries. Caller must hold d's grant table lock. */
@@ -582,7 +615,8 @@ static void mapcount(*/ ASSERT (rw_is_write_locked (&rd->grant_table->lock)); -for ( handle =0 ; handle < lgt->maptrack_limit; handle++ ) +for ( handle =0 ; handle < lgt->maptrack_pages *MAPTRACK_PER_PAGE ; + handle++ ) { map = &maptrack_entry (lgt, handle); if ( !(map->flags & (GNTMAP_device_map|GNTMAP_host_map)) ||
@@ -952,7 +986,7 @@ __gnttab_unmap_common( op->frame = (unsigned long )(op->dev_bus_addr >>PAGE_SHIFT ); -if (unlikely (op->handle >= lgt->maptrack_limit) ) +if (unlikely (op->handle >= lgt->maptrack_pages *MAPTRACK_PER_PAGE ) ) {gdprintk (XENLOG_INFO ,"Bad handle (%d).\n" , op->handle); op->status = GNTST_bad_handle;
@@ -1427,6 +1461,7 @@ gnttab_setup_table(struct gnttab_setup_table op;struct domain *d;struct grant_table *gt; +struct vcpu *v;int i;xen_pfn_t gmfn;
@@ -1468,6 +1503,17 @@ gnttab_setup_table( gt = d->grant_table;write_lock (>->lock); +/* Tracking of mapped foreign frames table */ + gt->maptrack =vzalloc (max_maptrack_frames *sizeof (*gt->maptrack)); +if ( gt->maptrack ==NULL ) + goto out3; +for_each_vcpu ( d, v ) + { + v->maptrack_head =MAPTRACK_TAIL ; + v->maptrack_tail =MAPTRACK_TAIL ; + } + gt->maptrack_pages =0 ; +if ( gt->gt_version ==0 ) gt->gt_version =1 ;
@@ -3061,18 +3107,6 @@ grant_table_create(spin_lock_init (&t->active[i][j].lock); } -/* Tracking of mapped foreign frames table */ - if ( (t->maptrack = xzalloc_array(struct grant_mapping *, - max_maptrack_frames)) ==NULL ) - goto no_mem_2; -if ( (t->maptrack[0 ] =alloc_xenheap_page ()) ==NULL ) - goto no_mem_3; -clear_page (t->maptrack[0 ]); - t->maptrack_limit =MAPTRACK_PER_PAGE ; -for ( i =1 ; i <MAPTRACK_PER_PAGE ; i++ ) - t->maptrack[0 ][i -1 ].ref = i; - t->maptrack[0 ][i -1 ].ref =MAPTRACK_TAIL ; -/* Shared grant table. */ if ( (t->shared_raw = xzalloc_array(void *, max_grant_frames)) == NULL ) goto no_mem_3;
@@ -3103,8 +3137,6 @@ grant_table_create(free_xenheap_page (t->shared_raw[i]);xfree (t->shared_raw); no_mem_3: -free_xenheap_page (t->maptrack[0 ]); -xfree (t->maptrack); no_mem_2:for ( i =0 ; i <num_act_frames_from_sha_frames (INITIAL_NR_GRANT_FRAMES ); i++ )
@@ -3132,7 +3164,8 @@ gnttab_release_mappings(BUG_ON (!d->is_dying); -for ( handle =0 ; handle < gt->maptrack_limit; handle++ ) +for ( handle =0 ; handle < gt->maptrack_pages *MAPTRACK_PER_PAGE ; + handle++ ) { map = &maptrack_entry (gt, handle);if ( !(map->flags & (GNTMAP_device_map|GNTMAP_host_map)) )
@@ -3237,9 +3270,9 @@ grant_table_destroy(free_xenheap_page (t->shared_raw[i]);xfree (t->shared_raw); -for ( i =0 ; i <nr_maptrack_frames (t); i++ ) +for ( i =0 ; i < t->maptrack_pages; i++ )free_xenheap_page (t->maptrack[i]); -xfree (t->maptrack); +vfree (t->maptrack);for ( i =0 ; i <nr_active_grant_frames (t); i++ )free_xenheap_page (t->active[i]);
xen/include/xen/grant_table.h +5 -3
@@ -60,6 +60,8 @@ struct grant_mapping {u32 ref;/* grant ref */ u16 flags;/* 0-4: GNTMAP_* ; 5-15: unused */ domid_t domid;/* granting domain */ +u32 vcpu;/* vcpu which created the grant mapping */ +u32 pad;/* round size to a power of 2 */ };/* Per-domain grant information. */
@@ -83,10 +85,10 @@ struct grant_table {grant_status_t **status;/* Active grant table. */ struct active_grant_entry **active; -/* Mapping tracking table. */ +/* Mapping tracking table per vcpu. */ struct grant_mapping **maptrack; -unsigned int maptrack_head; -unsigned int maptrack_limit; +/* Total pages used for mapping tracking table */ +unsigned int maptrack_pages;/* Lock protecting the maptrack page list, head, and limit */ spinlock_t maptrack_lock;/* The defined versions are 1 and 2. Set to 0 if we don't know
xen/include/xen/sched.h +5 -0
@@ -219,6 +219,11 @@ struct vcpu/* VCPU paused by system controller. */ int controller_pause_count; +/* Maptrack */ +unsigned int maptrack_head; +unsigned int maptrack_tail; +unsigned int maptrack_frames; +/* IRQ-safe virq_lock protects against delivering VIRQ to stale evtchn. */ evtchn_port_t virq_to_evtchn[NR_VIRQS ];spinlock_t virq_lock;
Signature · 1 line
1.7.10.4
RFC PATCH v12 5/5 gnttab: steal maptrack entries from other VCPUs
In reply to David VrabelIf a guest is not evenly grant mapping across its VCPUs one of the VCPUs may run out of free maptrack entries even though other VCPUs have many free.
If this happens, "steal" free entries from other VCPUs. We want to steal entries such that:
a) We avoid ping-ponging stolen entries between VCPUs.
b) The number of free entries owned by each VCPUs tends (over time) to the number it uses.
So when stealing, we select a VCPU at random (reducing (a)) and we transfer the stolen entries to the thief VCPU (aiming for (b)).
---
v12:
- New.
--- xen/common/grant_table.c | 73 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 15 deletions(-)
xen/common/grant_table.c +58 -15
@@ -277,26 +277,65 @@ __get_maptrack_handle(struct grant_table *t, struct vcpu *v) { - unsigned int head, next; + unsigned int head, next, prev_head; -/* No maptrack pages allocated for this VCPU yet? */ - head = v->maptrack_head; -if (unlikely (head ==MAPTRACK_TAIL ) ) -return -1 ; +do { +/* No maptrack pages allocated for this VCPU yet? */ + head =read_atomic (&v->maptrack_head); +if (unlikely (head ==MAPTRACK_TAIL ) ) +return -1 ; -/* -* Always keep one entry in the free list to make it easier to add -* free entries to the tail. -*/ - next =read_atomic (&maptrack_entry (t, head).ref); -if (unlikely (next ==MAPTRACK_TAIL ) ) -return -1 ; +/* +* Always keep one entry in the free list to make it easier to +* add free entries to the tail. +*/ + next =read_atomic (&maptrack_entry (t, head).ref); +if (unlikely (next ==MAPTRACK_TAIL ) ) +return -1 ; - v->maptrack_head = next; + prev_head = head; + head =cmpxchg (&v->maptrack_head, prev_head, next); + }while ( head != prev_head );return head; } +/* +* Try to "steal" a free maptrack entry from another VCPU. +* +* A stolen entry is transferred to the thief, so the number of +* entries for each VCPU should tend to the usage pattern. +* +* To avoid having to atomically count the number of free entries on +* each VCPU and to avoid two VCPU repeatedly stealing entries from +* each other, the initial victim VCPU is selected randomly. +*/ +static int steal_maptrack_handle (struct grant_table *t,struct vcpu *v) +{ +struct domain *d = v->domain; +unsigned int first, i; + +/* Find an initial victim. */ + first = i =NOW () % d->max_vcpus; + +do { +unsigned int handle; + + handle =__get_maptrack_handle (t, d->vcpu[i]); +if ( handle >=0 ) { +maptrack_entry (t, handle).vcpu = v->vcpu_id; +return handle; + } + + i++; +if ( i == d->max_vcpus ) + i =0 ; + }while ( i != first ); + +/* No free handles on any VCPU. */ +return -1 ; +} +static inline void put_maptrack_handle (struct grant_table *t,int handle)
@@ -337,10 +376,14 @@ get_maptrack_handle(/* * max_maptrack_frames is per domain so each VCPU gets a share of * the maximum, but allow at least one frame per VCPU. +* +* If this VCPU has run out of frames, try stealing an entry from +* another VCPU (in case the guest isn't mapping across its VCPUs +* evenly). */ if ( v->maptrack_frames && v->maptrack_frames >= max_maptrack_frames / v->domain->max_vcpus ) -return -1 ; +return steal_maptrack_handle (lgt, v); new_mt =alloc_xenheap_page ();if ( !new_mt )
@@ -358,7 +401,7 @@ get_maptrack_handle( new_mt[i -1 ].ref = v->maptrack_head; new_mt[i -1 ].vcpu = v->vcpu_id; - v->maptrack_head = lgt->maptrack_pages *MAPTRACK_PER_PAGE ; +write_atomic (&v->maptrack_head, lgt->maptrack_pages *MAPTRACK_PER_PAGE );/* Set tail directly if this is the first page for this VCPU. */ if ( v->maptrack_tail ==MAPTRACK_TAIL )
Signature · 1 line
1.7.10.4
Re: [PATCHv12 5/5] [RFC] gnttab: steal maptrack entries from other VCPUs
Quoted 4 lines
Quoted 1 line
Quoted 1 line · On 12.06.15 at 15:33, <[email protected]> wrote:
xen/common/grant_table.c +3
-0+static int steal_maptrack_handle (struct grant_table *t,struct vcpu *v) +{ +struct domain *d = v->domain;
xen/common/grant_table.c __get_maptrack_handle( · line 325
Quoted 10 lines
xen/common/grant_table.c +10
-0+unsigned int first, i; + +/* Find an initial victim. */ + first = i =NOW () % d->max_vcpus; + + do { + unsigned int handle; + + handle = __get_maptrack_handle(t, d->vcpu[i]); + if ( handle >= 0 ) {
Coding style.
xen/common/grant_table.c get_maptrack_handle( · line 386
Quoted 13 lines
xen/common/grant_table.c +5
-1@@ -337,10 +376,14 @@ get_maptrack_handle(/* * max_maptrack_frames is per domain so each VCPU gets a share of * the maximum, but allow at least one frame per VCPU. +* +* If this VCPU has run out of frames, try stealing an entry from +* another VCPU (in case the guest isn't mapping across its VCPUs +* evenly). */ if ( v->maptrack_frames && v->maptrack_frames >= max_maptrack_frames / v->domain->max_vcpus ) -return -1 ; +return steal_maptrack_handle (lgt, v);
This "allow at least one frame per vCPU" imo needs to go away again now.
Looks okay to me apart from that; quite nice an idea to utilize the fact that ownership of each entry is already being tracked.
Jan