simplify and fix futex requeuing:

don't wake up all the threads being requeued to have them move themselves
from one list to another (thus defeating the purpose), just change the lists
directly in futex_wake().
This commit is contained in:
chs 2016-05-20 13:54:34 +00:00
parent c41c6671b1
commit be6c83e099
1 changed files with 44 additions and 77 deletions

View File

@ -1,4 +1,4 @@
/* $NetBSD: linux_futex.c,v 1.33 2014/02/11 16:00:13 maxv Exp $ */
/* $NetBSD: linux_futex.c,v 1.34 2016/05/20 13:54:34 chs Exp $ */
/*-
* Copyright (c) 2005 Emmanuel Dreyfus, all rights reserved.
@ -32,7 +32,7 @@
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(1, "$NetBSD: linux_futex.c,v 1.33 2014/02/11 16:00:13 maxv Exp $");
__KERNEL_RCSID(1, "$NetBSD: linux_futex.c,v 1.34 2016/05/20 13:54:34 chs Exp $");
#include <sys/param.h>
#include <sys/time.h>
@ -58,11 +58,10 @@ __KERNEL_RCSID(1, "$NetBSD: linux_futex.c,v 1.33 2014/02/11 16:00:13 maxv Exp $"
struct futex;
struct waiting_proc {
lwp_t *wp_l;
struct futex *wp_new_futex;
struct futex *wp_futex;
kcondvar_t wp_futex_cv;
TAILQ_ENTRY(waiting_proc) wp_list;
TAILQ_ENTRY(waiting_proc) wp_rqlist;
bool wp_onlist;
};
struct futex {
void *f_uaddr;
@ -70,7 +69,6 @@ struct futex {
uint32_t f_bitset;
LIST_ENTRY(futex) f_list;
TAILQ_HEAD(, waiting_proc) f_waiting_proc;
TAILQ_HEAD(, waiting_proc) f_requeue_proc;
};
static LIST_HEAD(futex_list, futex) futex_list;
@ -432,7 +430,6 @@ futex_get(void *uaddr, uint32_t bitset)
f->f_bitset = bitset;
f->f_refcount = 1;
TAILQ_INIT(&f->f_waiting_proc);
TAILQ_INIT(&f->f_requeue_proc);
LIST_INSERT_HEAD(&futex_list, f, f_list);
return f;
@ -456,7 +453,6 @@ futex_put(struct futex *f)
f->f_refcount--;
if (f->f_refcount == 0) {
KASSERT(TAILQ_EMPTY(&f->f_waiting_proc));
KASSERT(TAILQ_EMPTY(&f->f_requeue_proc));
LIST_REMOVE(f, f_list);
kmem_free(f, sizeof(*f));
}
@ -465,107 +461,78 @@ futex_put(struct futex *f)
static int
futex_sleep(struct futex **fp, lwp_t *l, int timeout, struct waiting_proc *wp)
{
struct futex *f, *newf;
struct futex *f;
int ret;
FUTEX_LOCKASSERT;
f = *fp;
wp->wp_l = l;
wp->wp_new_futex = NULL;
requeue:
wp->wp_futex = f;
TAILQ_INSERT_TAIL(&f->f_waiting_proc, wp, wp_list);
wp->wp_onlist = true;
ret = cv_timedwait_sig(&wp->wp_futex_cv, &futex_lock, timeout);
TAILQ_REMOVE(&f->f_waiting_proc, wp, wp_list);
/* if futex_wake() tells us to requeue ... */
newf = wp->wp_new_futex;
if (ret == 0 && newf != NULL) {
/* ... requeue ourselves on the new futex */
futex_put(f);
wp->wp_new_futex = NULL;
TAILQ_REMOVE(&newf->f_requeue_proc, wp, wp_rqlist);
*fp = f = newf;
goto requeue;
/*
* we may have been requeued to a different futex before we were
* woken up, so let the caller know which futex to put. if we were
* woken by futex_wake() then it took us off the waiting list,
* but if our sleep was interrupted or timed out then we might
* need to take ourselves off the waiting list.
*/
f = wp->wp_futex;
if (wp->wp_onlist) {
TAILQ_REMOVE(&f->f_waiting_proc, wp, wp_list);
}
*fp = f;
return ret;
}
static int
futex_wake(struct futex *f, int n, struct futex *newf, int n2)
{
struct waiting_proc *wp, *wpnext;
int count;
struct waiting_proc *wp;
int count = 0;
FUTEX_LOCKASSERT;
count = newf ? 0 : 1;
/*
* first, wake up any threads sleeping on this futex.
* note that sleeping threads are not in the process of requeueing.
* wake up up to n threads waiting on this futex.
*/
TAILQ_FOREACH(wp, &f->f_waiting_proc, wp_list) {
KASSERT(wp->wp_new_futex == NULL);
while (n--) {
wp = TAILQ_FIRST(&f->f_waiting_proc);
if (wp == NULL)
return count;
FUTEXPRINTF(("%s: signal f %p l %p ref %d\n", __func__,
f, wp->wp_l, f->f_refcount));
KASSERT(f == wp->wp_futex);
TAILQ_REMOVE(&f->f_waiting_proc, wp, wp_list);
wp->wp_onlist = false;
cv_signal(&wp->wp_futex_cv);
if (count <= n) {
count++;
} else {
if (newf == NULL)
break;
/* matching futex_put() is called by the other thread. */
futex_ref(newf);
wp->wp_new_futex = newf;
TAILQ_INSERT_TAIL(&newf->f_requeue_proc, wp, wp_rqlist);
FUTEXPRINTF(("%s: requeue newf %p l %p ref %d\n",
__func__, newf, wp->wp_l, newf->f_refcount));
if (count - n >= n2)
goto out;
}
count++;
}
if (newf == NULL)
return count;
/*
* next, deal with threads that are requeuing to this futex.
* we don't need to signal these threads, any thread on the
* requeue list has already been signaled but hasn't had a chance
* to run and requeue itself yet. if we would normally wake
* a thread, just remove the requeue info. if we would normally
* requeue a thread, change the requeue target.
* then requeue up to n2 additional threads to newf
* (without waking them up).
*/
TAILQ_FOREACH_SAFE(wp, &f->f_requeue_proc, wp_rqlist, wpnext) {
KASSERT(wp->wp_new_futex == f);
while (n2--) {
wp = TAILQ_FIRST(&f->f_waiting_proc);
if (wp == NULL)
return count;
FUTEXPRINTF(("%s: unrequeue f %p l %p ref %d\n", __func__,
f, wp->wp_l, f->f_refcount));
wp->wp_new_futex = NULL;
TAILQ_REMOVE(&f->f_requeue_proc, wp, wp_rqlist);
KASSERT(f == wp->wp_futex);
TAILQ_REMOVE(&f->f_waiting_proc, wp, wp_list);
futex_put(f);
if (count <= n) {
count++;
} else {
if (newf == NULL)
break;
/* matching futex_put() is called by the other thread. */
futex_ref(newf);
wp->wp_new_futex = newf;
TAILQ_INSERT_TAIL(&newf->f_requeue_proc, wp, wp_rqlist);
FUTEXPRINTF(("%s: rerequeue newf %p l %p ref %d\n",
__func__, newf, wp->wp_l, newf->f_refcount));
if (count - n >= n2)
break;
}
wp->wp_futex = newf;
futex_ref(newf);
TAILQ_INSERT_TAIL(&newf->f_waiting_proc, wp, wp_list);
count++;
}
out:
return count;
}