mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-01-31 00:06:18 +01:00
304 lines
8.0 KiB
C
304 lines
8.0 KiB
C
/*
|
|
* Copyright © 2008 Ryan Lortie
|
|
* Copyright © 2010 Codethink Limited
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the licence, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*
|
|
* Author: Ryan Lortie <desrt@desrt.ca>
|
|
*/
|
|
|
|
#include "gbitlock.h"
|
|
|
|
#include <glib/gatomic.h>
|
|
#include <glib/gslist.h>
|
|
#include <glib/gthread.h>
|
|
|
|
#include "gthreadprivate.h"
|
|
#include "config.h"
|
|
|
|
#include "galias.h"
|
|
|
|
#ifdef G_BIT_LOCK_FORCE_FUTEX_EMULATION
|
|
#undef HAVE_FUTEX
|
|
#endif
|
|
|
|
#ifndef HAVE_FUTEX
|
|
static GSList *g_futex_address_list = NULL;
|
|
static GMutex *g_futex_mutex = NULL;
|
|
#endif
|
|
|
|
void
|
|
_g_futex_thread_init (void) {
|
|
#ifndef HAVE_FUTEX
|
|
g_futex_mutex = g_mutex_new ();
|
|
#endif
|
|
}
|
|
|
|
#ifdef HAVE_FUTEX
|
|
/*
|
|
* We have headers for futex(2) on the build machine. This does not
|
|
* imply that every system that ever runs the resulting glib will have
|
|
* kernel support for futex, but you'd have to have a pretty old
|
|
* kernel in order for that not to be the case.
|
|
*
|
|
* If anyone actually gets bit by this, please file a bug. :)
|
|
*/
|
|
#include <linux/futex.h>
|
|
#include <syscall.h>
|
|
#include <unistd.h>
|
|
|
|
/* < private >
|
|
* g_futex_wait:
|
|
* @address: a pointer to an integer
|
|
* @value: the value that should be at @address
|
|
*
|
|
* Atomically checks that the value stored at @address is equal to
|
|
* @value and then blocks. If the value stored at @address is not
|
|
* equal to @value then this function returns immediately.
|
|
*
|
|
* To unblock, call g_futex_wake() on @address.
|
|
*
|
|
* This call may spuriously unblock (for example, in response to the
|
|
* process receiving a signal) but this is not guaranteed. Unlike the
|
|
* Linux system call of a similar name, there is no guarantee that a
|
|
* waiting process will unblock due to a g_futex_wake() call in a
|
|
* separate process.
|
|
*/
|
|
static void
|
|
g_futex_wait (const volatile gint *address,
|
|
gint value)
|
|
{
|
|
syscall (SYS_futex, address, (gsize) FUTEX_WAIT, (gsize) value, NULL);
|
|
}
|
|
|
|
/* < private >
|
|
* g_futex_wake:
|
|
* @address: a pointer to an integer
|
|
*
|
|
* Nominally, wakes one thread that is blocked in g_futex_wait() on
|
|
* @address (if any thread is currently waiting).
|
|
*
|
|
* As mentioned in the documention for g_futex_wait(), spurious
|
|
* wakeups may occur. As such, this call may result in more than one
|
|
* thread being woken up.
|
|
*/
|
|
static void
|
|
g_futex_wake (const volatile gint *address)
|
|
{
|
|
syscall (SYS_futex, address, (gsize) FUTEX_WAKE, (gsize) 1, NULL);
|
|
}
|
|
|
|
#else
|
|
|
|
/* emulate futex(2) */
|
|
typedef struct
|
|
{
|
|
const volatile gint *address;
|
|
gint ref_count;
|
|
GCond *wait_queue;
|
|
} WaitAddress;
|
|
|
|
static GSList *g_futex_address_list;
|
|
static GMutex *g_futex_mutex;
|
|
|
|
static WaitAddress *
|
|
g_futex_find_address (const volatile gint *address)
|
|
{
|
|
GSList *node;
|
|
|
|
for (node = g_futex_address_list; node; node = node->next)
|
|
{
|
|
WaitAddress *waiter = node->data;
|
|
|
|
if (waiter->address == address)
|
|
return waiter;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
g_futex_wait (const volatile gint *address,
|
|
gint value)
|
|
{
|
|
g_mutex_lock (g_futex_mutex);
|
|
if G_LIKELY (g_atomic_int_get (address) == value)
|
|
{
|
|
WaitAddress *waiter;
|
|
|
|
if ((waiter = g_futex_find_address (address)) == NULL)
|
|
{
|
|
waiter = g_slice_new (WaitAddress);
|
|
waiter->address = address;
|
|
waiter->wait_queue = g_cond_new ();
|
|
waiter->ref_count = 0;
|
|
g_futex_address_list =
|
|
g_slist_prepend (g_futex_address_list, waiter);
|
|
}
|
|
|
|
waiter->ref_count++;
|
|
g_cond_wait (waiter->wait_queue, g_futex_mutex);
|
|
|
|
if (!--waiter->ref_count)
|
|
{
|
|
g_futex_address_list =
|
|
g_slist_remove (g_futex_address_list, waiter);
|
|
g_cond_free (waiter->wait_queue);
|
|
g_slice_free (WaitAddress, waiter);
|
|
}
|
|
}
|
|
g_mutex_unlock (g_futex_mutex);
|
|
}
|
|
|
|
static void
|
|
g_futex_wake (const volatile gint *address)
|
|
{
|
|
WaitAddress *waiter;
|
|
|
|
/* need to lock here for two reasons:
|
|
* 1) need to acquire/release lock to ensure waiter is not in
|
|
* the process of registering a wait
|
|
* 2) need to -stay- locked until the end to ensure a wake()
|
|
* in another thread doesn't cause 'waiter' to stop existing
|
|
*/
|
|
g_mutex_lock (g_futex_mutex);
|
|
if ((waiter = g_futex_find_address (address)))
|
|
g_cond_signal (waiter->wait_queue);
|
|
g_mutex_unlock (g_futex_mutex);
|
|
}
|
|
#endif
|
|
|
|
#define CONTENTION_CLASSES 11
|
|
static volatile gint g_bit_lock_contended[CONTENTION_CLASSES];
|
|
|
|
/**
|
|
* g_bit_lock:
|
|
* @address: a pointer to an integer
|
|
* @lock_bit: a bit value between 0 and 31
|
|
*
|
|
* Sets the indicated @lock_bit in @address. If the bit is already
|
|
* set, this call will block until g_bit_unlock() unsets the
|
|
* corresponding bit.
|
|
*
|
|
* Attempting to lock on two different bits within the same integer is
|
|
* not supported and will very probably cause deadlocks.
|
|
*
|
|
* The value of the bit that is set is (1u << @bit). If @bit is not
|
|
* between 0 and 31 then the result is undefined.
|
|
*
|
|
* This function accesses @address atomically. All other accesses to
|
|
* @address must be atomic in order for this function to work
|
|
* reliably.
|
|
*
|
|
* Since: 2.24
|
|
**/
|
|
void
|
|
g_bit_lock (volatile gint *address,
|
|
gint lock_bit)
|
|
{
|
|
guint v;
|
|
|
|
retry:
|
|
v = g_atomic_int_get (address);
|
|
if (v & (1u << lock_bit))
|
|
/* already locked */
|
|
{
|
|
guint class = ((gsize) address) % G_N_ELEMENTS (g_bit_lock_contended);
|
|
|
|
g_atomic_int_add (&g_bit_lock_contended[class], +1);
|
|
g_futex_wait (address, v);
|
|
g_atomic_int_add (&g_bit_lock_contended[class], -1);
|
|
|
|
goto retry;
|
|
}
|
|
|
|
if (!g_atomic_int_compare_and_exchange (address, v, v | (1u << lock_bit)))
|
|
goto retry;
|
|
}
|
|
|
|
/**
|
|
* g_bit_trylock:
|
|
* @address: a pointer to an integer
|
|
* @lock_bit: a bit value between 0 and 31
|
|
* @returns: %TRUE if the lock was acquired
|
|
*
|
|
* Sets the indicated @lock_bit in @address, returning %TRUE if
|
|
* successful. If the bit is already set, returns %FALSE immediately.
|
|
*
|
|
* Attempting to lock on two different bits within the same integer is
|
|
* not supported.
|
|
*
|
|
* The value of the bit that is set is (1u << @bit). If @bit is not
|
|
* between 0 and 31 then the result is undefined.
|
|
*
|
|
* This function accesses @address atomically. All other accesses to
|
|
* @address must be atomic in order for this function to work
|
|
* reliably.
|
|
*
|
|
* Since: 2.24
|
|
**/
|
|
gboolean
|
|
g_bit_trylock (volatile gint *address,
|
|
gint lock_bit)
|
|
{
|
|
guint v;
|
|
|
|
retry:
|
|
v = g_atomic_int_get (address);
|
|
if (v & (1u << lock_bit))
|
|
/* already locked */
|
|
return FALSE;
|
|
|
|
if (!g_atomic_int_compare_and_exchange (address, v, v | (1u << lock_bit)))
|
|
goto retry;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* g_bit_unlock:
|
|
* @address: a pointer to an integer
|
|
* @lock_bit: a bit value between 0 and 31
|
|
*
|
|
* Clears the indicated @lock_bit in @address. If another thread is
|
|
* currently blocked in g_bit_lock() on this same bit then it will be
|
|
* woken up.
|
|
*
|
|
* This function accesses @address atomically. All other accesses to
|
|
* @address must be atomic in order for this function to work
|
|
* reliably.
|
|
*
|
|
* Since: 2.24
|
|
**/
|
|
void
|
|
g_bit_unlock (volatile gint *address,
|
|
gint lock_bit)
|
|
{
|
|
guint class = ((gsize) address) % G_N_ELEMENTS (g_bit_lock_contended);
|
|
guint v;
|
|
|
|
retry:
|
|
v = g_atomic_int_get (address);
|
|
if (!g_atomic_int_compare_and_exchange (address, v, v & ~(1u << lock_bit)))
|
|
goto retry;
|
|
|
|
if (g_atomic_int_get (&g_bit_lock_contended[class]))
|
|
g_futex_wake (address);
|
|
}
|
|
|
|
#define __G_BITLOCK_C__
|
|
#include "galiasdef.c"
|