aboutsummaryrefslogtreecommitdiff
path: root/src/backend/storage/lmgr/spin.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/storage/lmgr/spin.c')
-rw-r--r--src/backend/storage/lmgr/spin.c397
1 files changed, 397 insertions, 0 deletions
diff --git a/src/backend/storage/lmgr/spin.c b/src/backend/storage/lmgr/spin.c
new file mode 100644
index 00000000000..a1284cbbcae
--- /dev/null
+++ b/src/backend/storage/lmgr/spin.c
@@ -0,0 +1,397 @@
+/*-------------------------------------------------------------------------
+ *
+ * spin.c
+ * routines for managing spin locks
+ *
+ * POSTGRES has two kinds of locks: semaphores (which put the
+ * process to sleep) and spinlocks (which are supposed to be
+ * short term locks). Spinlocks are implemented via test-and-set (TAS)
+ * instructions if possible, else via semaphores. The semaphore method
+ * is too slow to be useful :-(
+ *
+ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * $Header: /cvsroot/pgsql/src/backend/storage/lmgr/spin.c,v 1.1 2001/09/27 19:10:02 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <errno.h>
+#if !defined(HAS_TEST_AND_SET) && defined(HAVE_SYS_SEM_H)
+#include <sys/sem.h>
+#endif
+
+#include "miscadmin.h"
+#include "storage/proc.h"
+#include "storage/s_lock.h"
+
+
+/* Probably should move these to an appropriate header file */
+extern SPINLOCK BufMgrLock;
+extern SPINLOCK OidGenLockId;
+extern SPINLOCK XidGenLockId;
+extern SPINLOCK ControlFileLockId;
+extern SPINLOCK ShmemLock;
+extern SPINLOCK ShmemIndexLock;
+extern SPINLOCK LockMgrLock;
+extern SPINLOCK SInvalLock;
+extern SPINLOCK ProcStructLock;
+extern SPINLOCK FreeSpaceLock;
+#ifdef STABLE_MEMORY_STORAGE
+extern SPINLOCK MMCacheLock;
+#endif
+
+
+/*
+ * Initialize identifiers for permanent spinlocks during startup
+ *
+ * The same identifiers are used for both TAS and semaphore implementations,
+ * although in one case they are indexes into a shmem array and in the other
+ * they are semaphore numbers.
+ */
+static void
+InitSpinLockIDs(void)
+{
+ BufMgrLock = (SPINLOCK) BUFMGRLOCKID;
+ OidGenLockId = (SPINLOCK) OIDGENLOCKID;
+ XidGenLockId = (SPINLOCK) XIDGENLOCKID;
+ ControlFileLockId = (SPINLOCK) CNTLFILELOCKID;
+ ShmemLock = (SPINLOCK) SHMEMLOCKID;
+ ShmemIndexLock = (SPINLOCK) SHMEMINDEXLOCKID;
+ LockMgrLock = (SPINLOCK) LOCKMGRLOCKID;
+ SInvalLock = (SPINLOCK) SINVALLOCKID;
+ ProcStructLock = (SPINLOCK) PROCSTRUCTLOCKID;
+ FreeSpaceLock = (SPINLOCK) FREESPACELOCKID;
+#ifdef STABLE_MEMORY_STORAGE
+ MMCacheLock = (SPINLOCK) MMCACHELOCKID;
+#endif
+}
+
+
+#ifdef HAS_TEST_AND_SET
+
+/* real spin lock implementation */
+
+typedef struct slock
+{
+ slock_t shlock;
+} SLock;
+
+#ifdef LOCK_DEBUG
+bool Trace_spinlocks = false;
+
+inline static void
+PRINT_SLDEBUG(const char *where, SPINLOCK lockid, const SLock *lock)
+{
+ if (Trace_spinlocks)
+ elog(DEBUG, "%s: id=%d", where, lockid);
+}
+
+#else /* not LOCK_DEBUG */
+#define PRINT_SLDEBUG(a,b,c)
+#endif /* not LOCK_DEBUG */
+
+
+static SLock *SLockArray = NULL;
+
+#define SLOCKMEMORYSIZE ((int) MAX_SPINS * sizeof(SLock))
+
+/*
+ * SLockShmemSize --- return shared-memory space needed
+ */
+int
+SLockShmemSize(void)
+{
+ return MAXALIGN(SLOCKMEMORYSIZE);
+}
+
+/*
+ * CreateSpinlocks --- create and initialize spinlocks during startup
+ */
+void
+CreateSpinlocks(PGShmemHeader *seghdr)
+{
+ int id;
+
+ /*
+ * We must allocate the space "by hand" because shmem.c isn't up yet
+ */
+ SLockArray = (SLock *) (((char *) seghdr) + seghdr->freeoffset);
+ seghdr->freeoffset += MAXALIGN(SLOCKMEMORYSIZE);
+ Assert(seghdr->freeoffset <= seghdr->totalsize);
+
+ /*
+ * Initialize all spinlocks to "unlocked" state
+ */
+ for (id = 0; id < (int) MAX_SPINS; id++)
+ {
+ SLock *slckP = &(SLockArray[id]);
+
+ S_INIT_LOCK(&(slckP->shlock));
+ }
+
+ /*
+ * Assign indexes for fixed spinlocks
+ */
+ InitSpinLockIDs();
+}
+
+void
+SpinAcquire(SPINLOCK lockid)
+{
+ SLock *slckP = &(SLockArray[lockid]);
+
+ PRINT_SLDEBUG("SpinAcquire", lockid, slckP);
+
+ /*
+ * Acquire the lock, then record that we have done so (for recovery in
+ * case of elog(ERROR) while holding the lock). Note we assume here
+ * that S_LOCK will not accept cancel/die interrupts once it has
+ * acquired the lock. However, interrupts should be accepted while
+ * waiting, if InterruptHoldoffCount is zero.
+ */
+ S_LOCK(&(slckP->shlock));
+ PROC_INCR_SLOCK(lockid);
+
+ /*
+ * Lock out cancel/die interrupts until we exit the code section
+ * protected by the spinlock. This ensures that interrupts will not
+ * interfere with manipulations of data structures in shared memory.
+ */
+ HOLD_INTERRUPTS();
+
+ PRINT_SLDEBUG("SpinAcquire/done", lockid, slckP);
+}
+
+void
+SpinRelease(SPINLOCK lockid)
+{
+ SLock *slckP = &(SLockArray[lockid]);
+
+ PRINT_SLDEBUG("SpinRelease", lockid, slckP);
+
+ /*
+ * Check that we are actually holding the lock we are releasing. This
+ * can be done only after MyProc has been initialized.
+ */
+ Assert(!MyProc || MyProc->sLocks[lockid] > 0);
+
+ /*
+ * Record that we no longer hold the spinlock, and release it.
+ */
+ PROC_DECR_SLOCK(lockid);
+ S_UNLOCK(&(slckP->shlock));
+
+ /*
+ * Exit the interrupt holdoff entered in SpinAcquire().
+ */
+ RESUME_INTERRUPTS();
+
+ PRINT_SLDEBUG("SpinRelease/done", lockid, slckP);
+}
+
+#else /* !HAS_TEST_AND_SET */
+
+/*
+ * No TAS, so spinlocks are implemented using SysV semaphores.
+ *
+ * We support two slightly different APIs here: SpinAcquire/SpinRelease
+ * work with SPINLOCK integer indexes for the permanent spinlocks, which
+ * are all assumed to live in the first spinlock semaphore set. There
+ * is also an emulation of the s_lock.h TAS-spinlock macros; for that case,
+ * typedef slock_t stores the semId and sem number of the sema to use.
+ * The semas needed are created by CreateSpinlocks and doled out by
+ * s_init_lock_sema.
+ *
+ * Since many systems have a rather small SEMMSL limit on semas per set,
+ * we allocate the semaphores required in sets of SPINLOCKS_PER_SET semas.
+ * This value is deliberately made equal to PROC_NSEMS_PER_SET so that all
+ * sema sets allocated by Postgres will be the same size; that eases the
+ * semaphore-recycling logic in IpcSemaphoreCreate().
+ *
+ * Note that the SpinLockIds array is not in shared memory; it is filled
+ * by the postmaster and then inherited through fork() by backends. This
+ * is OK because its contents do not change after shmem initialization.
+ */
+
+#define SPINLOCKS_PER_SET PROC_NSEMS_PER_SET
+
+static IpcSemaphoreId *SpinLockIds = NULL;
+
+static int numSpinSets = 0; /* number of sema sets used */
+static int numSpinLocks = 0; /* total number of semas allocated */
+static int nextSpinLock = 0; /* next free spinlock index */
+
+static void SpinFreeAllSemaphores(void);
+
+/*
+ * SLockShmemSize --- return shared-memory space needed
+ */
+int
+SLockShmemSize(void)
+{
+ return 0;
+}
+
+/*
+ * CreateSpinlocks --- create and initialize spinlocks during startup
+ */
+void
+CreateSpinlocks(PGShmemHeader *seghdr)
+{
+ int i;
+
+ if (SpinLockIds == NULL)
+ {
+
+ /*
+ * Compute number of spinlocks needed. If this logic gets any
+ * more complicated, it should be distributed into the affected
+ * modules, similar to the way shmem space estimation is handled.
+ *
+ * For now, though, we just need the fixed spinlocks (MAX_SPINS), two
+ * spinlocks per shared disk buffer, and four spinlocks for XLOG.
+ */
+ numSpinLocks = (int) MAX_SPINS + 2 * NBuffers + 4;
+
+ /* might as well round up to a multiple of SPINLOCKS_PER_SET */
+ numSpinSets = (numSpinLocks - 1) / SPINLOCKS_PER_SET + 1;
+ numSpinLocks = numSpinSets * SPINLOCKS_PER_SET;
+
+ SpinLockIds = (IpcSemaphoreId *)
+ malloc(numSpinSets * sizeof(IpcSemaphoreId));
+ Assert(SpinLockIds != NULL);
+ }
+
+ for (i = 0; i < numSpinSets; i++)
+ SpinLockIds[i] = -1;
+
+ /*
+ * Arrange to delete semas on exit --- set this up now so that we will
+ * clean up if allocation fails. We use our own freeproc, rather than
+ * IpcSemaphoreCreate's removeOnExit option, because we don't want to
+ * fill up the on_shmem_exit list with a separate entry for each
+ * semaphore set.
+ */
+ on_shmem_exit(SpinFreeAllSemaphores, 0);
+
+ /* Create sema sets and set all semas to count 1 */
+ for (i = 0; i < numSpinSets; i++)
+ {
+ SpinLockIds[i] = IpcSemaphoreCreate(SPINLOCKS_PER_SET,
+ IPCProtection,
+ 1,
+ false);
+ }
+
+ /*
+ * Assign indexes for fixed spinlocks
+ */
+ Assert(MAX_SPINS <= SPINLOCKS_PER_SET);
+ InitSpinLockIDs();
+
+ /* Init counter for allocating dynamic spinlocks */
+ nextSpinLock = MAX_SPINS;
+}
+
+/*
+ * SpinFreeAllSemaphores -
+ * called at shmem_exit time, ie when exiting the postmaster or
+ * destroying shared state for a failed set of backends.
+ * Free up all the semaphores allocated for spinlocks.
+ */
+static void
+SpinFreeAllSemaphores(void)
+{
+ int i;
+
+ for (i = 0; i < numSpinSets; i++)
+ {
+ if (SpinLockIds[i] >= 0)
+ IpcSemaphoreKill(SpinLockIds[i]);
+ }
+ free(SpinLockIds);
+ SpinLockIds = NULL;
+}
+
+/*
+ * SpinAcquire -- grab a fixed spinlock
+ *
+ * FAILS if the semaphore is corrupted.
+ */
+void
+SpinAcquire(SPINLOCK lock)
+{
+
+ /*
+ * See the TAS() version of this routine for primary commentary.
+ *
+ * NOTE we must pass interruptOK = false to IpcSemaphoreLock, to ensure
+ * that a cancel/die interrupt cannot prevent us from recording
+ * ownership of a lock we have just acquired.
+ */
+ IpcSemaphoreLock(SpinLockIds[0], lock, false);
+ PROC_INCR_SLOCK(lock);
+ HOLD_INTERRUPTS();
+}
+
+/*
+ * SpinRelease -- release a fixed spin lock
+ *
+ * FAILS if the semaphore is corrupted
+ */
+void
+SpinRelease(SPINLOCK lock)
+{
+ /* See the TAS() version of this routine for commentary */
+#ifdef USE_ASSERT_CHECKING
+ /* Check it's locked */
+ int semval;
+
+ semval = IpcSemaphoreGetValue(SpinLockIds[0], lock);
+ Assert(semval < 1);
+#endif
+ Assert(!MyProc || MyProc->sLocks[lockid] > 0);
+ PROC_DECR_SLOCK(lock);
+ IpcSemaphoreUnlock(SpinLockIds[0], lock);
+ RESUME_INTERRUPTS();
+}
+
+/*
+ * s_lock.h hardware-spinlock emulation
+ */
+
+void
+s_init_lock_sema(volatile slock_t *lock)
+{
+ if (nextSpinLock >= numSpinLocks)
+ elog(FATAL, "s_init_lock_sema: not enough semaphores");
+ lock->semId = SpinLockIds[nextSpinLock / SPINLOCKS_PER_SET];
+ lock->sem = nextSpinLock % SPINLOCKS_PER_SET;
+ nextSpinLock++;
+}
+
+void
+s_unlock_sema(volatile slock_t *lock)
+{
+ IpcSemaphoreUnlock(lock->semId, lock->sem);
+}
+
+bool
+s_lock_free_sema(volatile slock_t *lock)
+{
+ return IpcSemaphoreGetValue(lock->semId, lock->sem) > 0;
+}
+
+int
+tas_sema(volatile slock_t *lock)
+{
+ /* Note that TAS macros return 0 if *success* */
+ return !IpcSemaphoreTryLock(lock->semId, lock->sem);
+}
+
+#endif /* !HAS_TEST_AND_SET */