| /* |
| * 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 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" |