diff options
Diffstat (limited to 'src/backend/storage/lmgr/spin.c')
-rw-r--r-- | src/backend/storage/lmgr/spin.c | 397 |
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 */ |