Skip to content

Commit

Permalink
xen: speed up grant-table reclaim
Browse files Browse the repository at this point in the history
When a grant entry is still in use by the remote domain, Linux must put
it on a deferred list.  Normally, this list is very short, because
the PV network and block protocols expect the backend to unmap the grant
first.  However, Qubes OS's GUI protocol is subject to the constraints
of the X Window System, and as such winds up with the frontend unmapping
the window first.  As a result, the list can grow very large, resulting
in a massive memory leak and eventual VM freeze.

To partially solve this problem, make the number of entries that the VM
will attempt to free at each iteration tunable.  The default is still
10, but it can be overridden via a module parameter.

This is Cc: stable because (when combined with appropriate userspace
changes) it fixes a severe performance and stability problem for Qubes
OS users.

Cc: stable@vger.kernel.org
Signed-off-by: Demi Marie Obenour <demi@invisiblethingslab.com>
Reviewed-by: Juergen Gross <jgross@suse.com>
Link: https://lore.kernel.org/r/20230726165354.1252-1-demi@invisiblethingslab.com
Signed-off-by: Juergen Gross <jgross@suse.com>
  • Loading branch information
Demi Marie Obenour authored and Juergen Gross committed Jul 27, 2023
1 parent 58f6259 commit c04e989
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 11 deletions.
11 changes: 11 additions & 0 deletions Documentation/ABI/testing/sysfs-module
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,14 @@ Description: Module taint flags:
C staging driver module
E unsigned module
== =====================

What: /sys/module/grant_table/parameters/free_per_iteration
Date: July 2023
KernelVersion: 6.5 but backported to all supported stable branches
Contact: Xen developer discussion <xen-devel@lists.xenproject.org>
Description: Read and write number of grant entries to attempt to free per iteration.

Note: Future versions of Xen and Linux may provide a better
interface for controlling the rate of deferred grant reclaim
or may not need it at all.
Users: Qubes OS (https://www.qubes-os.org)
40 changes: 29 additions & 11 deletions drivers/xen/grant-table.c
Original file line number Diff line number Diff line change
Expand Up @@ -498,14 +498,21 @@ static LIST_HEAD(deferred_list);
static void gnttab_handle_deferred(struct timer_list *);
static DEFINE_TIMER(deferred_timer, gnttab_handle_deferred);

static atomic64_t deferred_count;
static atomic64_t leaked_count;
static unsigned int free_per_iteration = 10;
module_param(free_per_iteration, uint, 0600);

static void gnttab_handle_deferred(struct timer_list *unused)
{
unsigned int nr = 10;
unsigned int nr = READ_ONCE(free_per_iteration);
const bool ignore_limit = nr == 0;
struct deferred_entry *first = NULL;
unsigned long flags;
size_t freed = 0;

spin_lock_irqsave(&gnttab_list_lock, flags);
while (nr--) {
while ((ignore_limit || nr--) && !list_empty(&deferred_list)) {
struct deferred_entry *entry
= list_first_entry(&deferred_list,
struct deferred_entry, list);
Expand All @@ -515,10 +522,14 @@ static void gnttab_handle_deferred(struct timer_list *unused)
list_del(&entry->list);
spin_unlock_irqrestore(&gnttab_list_lock, flags);
if (_gnttab_end_foreign_access_ref(entry->ref)) {
uint64_t ret = atomic64_dec_return(&deferred_count);

put_free_entry(entry->ref);
pr_debug("freeing g.e. %#x (pfn %#lx)\n",
entry->ref, page_to_pfn(entry->page));
pr_debug("freeing g.e. %#x (pfn %#lx), %llu remaining\n",
entry->ref, page_to_pfn(entry->page),
(unsigned long long)ret);
put_page(entry->page);
freed++;
kfree(entry);
entry = NULL;
} else {
Expand All @@ -530,21 +541,22 @@ static void gnttab_handle_deferred(struct timer_list *unused)
spin_lock_irqsave(&gnttab_list_lock, flags);
if (entry)
list_add_tail(&entry->list, &deferred_list);
else if (list_empty(&deferred_list))
break;
}
if (!list_empty(&deferred_list) && !timer_pending(&deferred_timer)) {
if (list_empty(&deferred_list))
WARN_ON(atomic64_read(&deferred_count));
else if (!timer_pending(&deferred_timer)) {
deferred_timer.expires = jiffies + HZ;
add_timer(&deferred_timer);
}
spin_unlock_irqrestore(&gnttab_list_lock, flags);
pr_debug("Freed %zu references", freed);
}

static void gnttab_add_deferred(grant_ref_t ref, struct page *page)
{
struct deferred_entry *entry;
gfp_t gfp = (in_atomic() || irqs_disabled()) ? GFP_ATOMIC : GFP_KERNEL;
const char *what = KERN_WARNING "leaking";
uint64_t leaked, deferred;

entry = kmalloc(sizeof(*entry), gfp);
if (!page) {
Expand All @@ -567,10 +579,16 @@ static void gnttab_add_deferred(grant_ref_t ref, struct page *page)
add_timer(&deferred_timer);
}
spin_unlock_irqrestore(&gnttab_list_lock, flags);
what = KERN_DEBUG "deferring";
deferred = atomic64_inc_return(&deferred_count);
leaked = atomic64_read(&leaked_count);
pr_debug("deferring g.e. %#x (pfn %#lx) (total deferred %llu, total leaked %llu)\n",
ref, page ? page_to_pfn(page) : -1, deferred, leaked);
} else {
deferred = atomic64_read(&deferred_count);
leaked = atomic64_inc_return(&leaked_count);
pr_warn("leaking g.e. %#x (pfn %#lx) (total deferred %llu, total leaked %llu)\n",
ref, page ? page_to_pfn(page) : -1, deferred, leaked);
}
printk("%s g.e. %#x (pfn %#lx)\n",
what, ref, page ? page_to_pfn(page) : -1);
}

int gnttab_try_end_foreign_access(grant_ref_t ref)
Expand Down

0 comments on commit c04e989

Please sign in to comment.