freebsd-dev/sys/kern/kern_umtx.c
Marcel Moolenaar cd28f17da2 Change the thread ID (thr_id_t) used for 1:1 threading from being a
pointer to the corresponding struct thread to the thread ID (lwpid_t)
assigned to that thread. The primary reason for this change is that
libthr now internally uses the same ID as the debugger and the kernel
when referencing to a kernel thread. This allows us to implement the
support for debugging without additional translations and/or mappings.

To preserve the ABI, the 1:1 threading syscalls, including the umtx
locking API have not been changed to work on a lwpid_t. Instead the
1:1 threading syscalls operate on long and the umtx locking API has
not been changed except for the contested bit. Previously this was
the least significant bit. Now it's the most significant bit. Since
the contested bit should not be tested by userland, this change is
not expected to be visible. Just to be sure, UMTX_CONTESTED has been
removed from <sys/umtx.h>.

Reviewed by: mtm@
ABI preservation tested on: i386, ia64
2004-07-02 00:40:07 +00:00

334 lines
8.0 KiB
C

/*
* Copyright (c) 2002, Jeffrey Roberson <jeff@freebsd.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice unmodified, this list of conditions, and the following
* disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/limits.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/proc.h>
#include <sys/signalvar.h>
#include <sys/sysent.h>
#include <sys/systm.h>
#include <sys/sysproto.h>
#include <sys/sx.h>
#include <sys/thr.h>
#include <sys/umtx.h>
struct umtx_q {
LIST_ENTRY(umtx_q) uq_next; /* Linked list for the hash. */
TAILQ_HEAD(, thread) uq_tdq; /* List of threads blocked here. */
struct umtx *uq_umtx; /* Pointer key component. */
pid_t uq_pid; /* Pid key component. */
};
#define UMTX_QUEUES 128
#define UMTX_HASH(pid, umtx) \
(((uintptr_t)pid + ((uintptr_t)umtx & ~65535)) % UMTX_QUEUES)
LIST_HEAD(umtx_head, umtx_q);
static struct umtx_head queues[UMTX_QUEUES];
static MALLOC_DEFINE(M_UMTX, "umtx", "UMTX queue memory");
static struct mtx umtx_lock;
MTX_SYSINIT(umtx, &umtx_lock, "umtx", MTX_DEF);
#define UMTX_LOCK() mtx_lock(&umtx_lock);
#define UMTX_UNLOCK() mtx_unlock(&umtx_lock);
#define UMTX_CONTESTED LONG_MIN
static struct umtx_q *umtx_lookup(struct thread *, struct umtx *umtx);
static struct umtx_q *umtx_insert(struct thread *, struct umtx *umtx);
static struct umtx_q *
umtx_lookup(struct thread *td, struct umtx *umtx)
{
struct umtx_head *head;
struct umtx_q *uq;
pid_t pid;
pid = td->td_proc->p_pid;
head = &queues[UMTX_HASH(td->td_proc->p_pid, umtx)];
LIST_FOREACH(uq, head, uq_next) {
if (uq->uq_pid == pid && uq->uq_umtx == umtx)
return (uq);
}
return (NULL);
}
/*
* Insert a thread onto the umtx queue.
*/
static struct umtx_q *
umtx_insert(struct thread *td, struct umtx *umtx)
{
struct umtx_head *head;
struct umtx_q *uq;
pid_t pid;
pid = td->td_proc->p_pid;
if ((uq = umtx_lookup(td, umtx)) == NULL) {
struct umtx_q *ins;
UMTX_UNLOCK();
ins = malloc(sizeof(*uq), M_UMTX, M_ZERO | M_WAITOK);
UMTX_LOCK();
/*
* Some one else could have succeeded while we were blocked
* waiting on memory.
*/
if ((uq = umtx_lookup(td, umtx)) == NULL) {
head = &queues[UMTX_HASH(pid, umtx)];
uq = ins;
uq->uq_pid = pid;
uq->uq_umtx = umtx;
LIST_INSERT_HEAD(head, uq, uq_next);
TAILQ_INIT(&uq->uq_tdq);
} else
free(ins, M_UMTX);
}
/*
* Insert us onto the end of the TAILQ.
*/
TAILQ_INSERT_TAIL(&uq->uq_tdq, td, td_umtx);
return (uq);
}
static void
umtx_remove(struct umtx_q *uq, struct thread *td)
{
TAILQ_REMOVE(&uq->uq_tdq, td, td_umtx);
if (TAILQ_EMPTY(&uq->uq_tdq)) {
LIST_REMOVE(uq, uq_next);
free(uq, M_UMTX);
}
}
int
_umtx_lock(struct thread *td, struct _umtx_lock_args *uap)
/* struct umtx *umtx */
{
struct umtx_q *uq;
struct umtx *umtx;
intptr_t owner;
intptr_t old;
int error;
uq = NULL;
/*
* Care must be exercised when dealing with this structure. It
* can fault on any access.
*/
umtx = uap->umtx;
for (;;) {
/*
* Try the uncontested case. This should be done in userland.
*/
owner = casuptr((intptr_t *)&umtx->u_owner,
UMTX_UNOWNED, td->td_tid);
/* The address was invalid. */
if (owner == -1)
return (EFAULT);
/* The acquire succeeded. */
if (owner == UMTX_UNOWNED)
return (0);
/* If no one owns it but it is contested try to acquire it. */
if (owner == UMTX_CONTESTED) {
owner = casuptr((intptr_t *)&umtx->u_owner,
UMTX_CONTESTED, td->td_tid | UMTX_CONTESTED);
/* The address was invalid. */
if (owner == -1)
return (EFAULT);
if (owner == UMTX_CONTESTED)
return (0);
/* If this failed the lock has changed, restart. */
continue;
}
UMTX_LOCK();
uq = umtx_insert(td, umtx);
UMTX_UNLOCK();
/*
* Set the contested bit so that a release in user space
* knows to use the system call for unlock. If this fails
* either some one else has acquired the lock or it has been
* released.
*/
old = casuptr((intptr_t *)&umtx->u_owner, owner,
owner | UMTX_CONTESTED);
/* The address was invalid. */
if (old == -1) {
UMTX_LOCK();
umtx_remove(uq, td);
UMTX_UNLOCK();
return (EFAULT);
}
/*
* We set the contested bit, sleep. Otherwise the lock changed
* and we need to retry or we lost a race to the thread
* unlocking the umtx.
*/
PROC_LOCK(td->td_proc);
mtx_lock_spin(&sched_lock);
if (old == owner && (td->td_flags & TDF_UMTXWAKEUP) == 0) {
mtx_unlock_spin(&sched_lock);
error = msleep(td, &td->td_proc->p_mtx,
td->td_priority | PCATCH, "umtx", 0);
mtx_lock_spin(&sched_lock);
} else
error = 0;
td->td_flags &= ~TDF_UMTXWAKEUP;
mtx_unlock_spin(&sched_lock);
PROC_UNLOCK(td->td_proc);
UMTX_LOCK();
umtx_remove(uq, td);
UMTX_UNLOCK();
/*
* If we caught a signal we might have to retry or exit
* immediately.
*/
if (error)
return (error);
}
return (0);
}
int
_umtx_unlock(struct thread *td, struct _umtx_unlock_args *uap)
/* struct umtx *umtx */
{
struct thread *blocked;
struct umtx *umtx;
struct umtx_q *uq;
intptr_t owner;
intptr_t old;
umtx = uap->umtx;
/*
* Make sure we own this mtx.
*
* XXX Need a {fu,su}ptr this is not correct on arch where
* sizeof(intptr_t) != sizeof(long).
*/
if ((owner = fuword(&umtx->u_owner)) == -1)
return (EFAULT);
if ((owner & ~UMTX_CONTESTED) != td->td_tid)
return (EPERM);
/* We should only ever be in here for contested locks */
if ((owner & UMTX_CONTESTED) == 0)
return (EINVAL);
blocked = NULL;
/*
* When unlocking the umtx, it must be marked as unowned if
* there is zero or one thread only waiting for it.
* Otherwise, it must be marked as contested.
*/
UMTX_LOCK();
uq = umtx_lookup(td, umtx);
if (uq == NULL ||
(uq != NULL && (blocked = TAILQ_FIRST(&uq->uq_tdq)) != NULL &&
TAILQ_NEXT(blocked, td_umtx) == NULL)) {
UMTX_UNLOCK();
old = casuptr((intptr_t *)&umtx->u_owner, owner,
UMTX_UNOWNED);
if (old == -1)
return (EFAULT);
if (old != owner)
return (EINVAL);
/*
* Recheck the umtx queue to make sure another thread
* didn't put itself on it after it was unlocked.
*/
UMTX_LOCK();
uq = umtx_lookup(td, umtx);
if (uq != NULL &&
((blocked = TAILQ_FIRST(&uq->uq_tdq)) != NULL &&
TAILQ_NEXT(blocked, td_umtx) != NULL)) {
UMTX_UNLOCK();
old = casuptr((intptr_t *)&umtx->u_owner,
UMTX_UNOWNED, UMTX_CONTESTED);
} else {
UMTX_UNLOCK();
}
} else {
UMTX_UNLOCK();
old = casuptr((intptr_t *)&umtx->u_owner,
owner, UMTX_CONTESTED);
if (old != -1 && old != owner)
return (EINVAL);
}
if (old == -1)
return (EFAULT);
/*
* If there is a thread waiting on the umtx, wake it up.
*/
if (blocked != NULL) {
PROC_LOCK(blocked->td_proc);
mtx_lock_spin(&sched_lock);
blocked->td_flags |= TDF_UMTXWAKEUP;
mtx_unlock_spin(&sched_lock);
PROC_UNLOCK(blocked->td_proc);
wakeup(blocked);
}
return (0);
}