#define _GNU_SOURCE #define _XOPEN_SOURCE 600 #include "config.h" #include #include #include #include #include #include #ifdef HAVE_STDATOMIC_H # include #endif /* HAVE_STDATOMIC_H */ #include "error.h" #include "lock.h" #include "pci.h" /** * structure to define a lock */ struct pcilib_lock_s { pthread_mutex_t mutex; /**< the pthread robust mutex */ pcilib_lock_flags_t flags; /**< flags to define the type of the mutex */ #ifdef HAVE_STDATOMIC_H volatile atomic_uint refs; /**< approximate number of processes that hold the lock initialized, may desynchronize on crashes */ #else /* HAVE_STDATOMIC_H */ volatile uint32_t refs; /**< approximate number of processes that hold the lock initialized, may desynchronize on crashes */ #endif /* HAVE_STDATOMIC_H */ char name[]; /**< lock identifier */ }; int pcilib_init_lock(pcilib_lock_t *lock, pcilib_lock_flags_t flags, const char *lock_id) { int err; pthread_mutexattr_t attr; assert(lock); assert(lock_id); memset(lock, 0, PCILIB_LOCK_SIZE); if (strlen(lock_id) >= (PCILIB_LOCK_SIZE - offsetof(struct pcilib_lock_s, name))) { pcilib_error("The supplied lock id (%s) is too long...", lock_id); return PCILIB_ERROR_INVALID_ARGUMENT; } if ((err = pthread_mutexattr_init(&attr))!=0) { pcilib_error("Can't initialize mutex attribute, errno %i", errno); return PCILIB_ERROR_FAILED; } /* we declare the mutex as possibly shared amongst different processes*/ if ((err = pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED))!=0) { pcilib_error("Can't configure a shared mutex attribute, errno %i", errno); return PCILIB_ERROR_FAILED; } /* we set the mutex as robust, so it would be automatically unlocked if the application crash*/ if ((flags&PCILIB_LOCK_FLAG_PERSISTENT)==0) { if ((err = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST))!=0) { pcilib_error("Can't configure a robust mutex attribute, errno: %i", errno); return PCILIB_ERROR_FAILED; } } if ((err = pthread_mutex_init(&lock->mutex, &attr))!=0) { pcilib_error("Can't create mutex, errno : %i",errno); return PCILIB_ERROR_FAILED; } strcpy(lock->name, lock_id); lock->refs = 1; lock->flags = flags; return 0; } void pcilib_free_lock(pcilib_lock_t *lock) { int err; assert(lock); // if (lock->refs) // pcilib_error("Forbidding to destroy the referenced mutex..."); if ((err = pthread_mutex_destroy(&lock->mutex))==-1) pcilib_warning("Can't destroy POSIX mutex, errno %i",errno); } void pcilib_lock_ref(pcilib_lock_t *lock) { assert(lock); #ifdef HAVE_STDATOMIC_H atomic_fetch_add_explicit(&lock->refs, 1, memory_order_relaxed); #else /* HAVE_STDATOMIC_H */ lock->refs++; #endif /* HAVE_STDATOMIC_H */ } void pcilib_lock_unref(pcilib_lock_t *lock) { assert(lock); if (!lock->refs) { pcilib_warning("Lock is not referenced"); return; } #ifdef HAVE_STDATOMIC_H atomic_fetch_sub_explicit(&lock->refs, 1, memory_order_relaxed); #else /* HAVE_STDATOMIC_H */ lock->refs--; #endif /* HAVE_STDATOMIC_H */ } size_t pcilib_lock_get_refs(pcilib_lock_t *lock) { return lock->refs; } pcilib_lock_flags_t pcilib_lock_get_flags(pcilib_lock_t *lock) { return lock->flags; } const char *pcilib_lock_get_name(pcilib_lock_t *lock) { assert(lock); if (lock->name[0]) return lock->name; return NULL; } int pcilib_lock_custom(pcilib_lock_t *lock, pcilib_lock_flags_t flags, pcilib_timeout_t timeout) { int err; if (!lock) { pcilib_error("The null lock pointer is passed to lock function"); return PCILIB_ERROR_INVALID_ARGUMENT; } struct timespec tm; switch (timeout) { case PCILIB_TIMEOUT_INFINITE: /* the process will be hold till it can gain acquire the lock*/ err = pthread_mutex_lock(&lock->mutex); break; case PCILIB_TIMEOUT_IMMEDIATE: /* the function returns immediatly if it can't acquire the lock*/ err = pthread_mutex_trylock(&lock->mutex); break; default: /* the process will be hold till it can acquire the lock and timeout is not reached*/ clock_gettime(CLOCK_REALTIME, &tm); tm.tv_nsec += 1000 * (timeout%1000000); if (tm.tv_nsec < 1000000000) tm.tv_sec += timeout/1000000; else { tm.tv_sec += 1 + timeout/1000000; tm.tv_nsec -= 1000000000; } err = pthread_mutex_timedlock(&lock->mutex, &tm); } if (!err) return 0; switch (err) { case EOWNERDEAD: /*in the case an application with a lock acquired crashes, this lock becomes inconsistent. we have so to make it consistent again to use it again.*/ err = pthread_mutex_consistent(&lock->mutex); if (err) { pcilib_error("Failed to mark mutex as consistent, errno %i", err); break; } return 0; case ETIMEDOUT: case EBUSY: return PCILIB_ERROR_TIMEOUT; default: pcilib_error("Failed to obtain mutex, errno %i", err); } return PCILIB_ERROR_FAILED; } int pcilib_lock(pcilib_lock_t* lock) { return pcilib_lock_custom(lock, PCILIB_LOCK_FLAGS_DEFAULT, PCILIB_TIMEOUT_INFINITE); } int pcilib_try_lock(pcilib_lock_t* lock) { return pcilib_lock_custom(lock, PCILIB_LOCK_FLAGS_DEFAULT, PCILIB_TIMEOUT_IMMEDIATE); } void pcilib_unlock(pcilib_lock_t *lock) { int err; if (!lock) return; if ((err = pthread_mutex_unlock(&lock->mutex)) != 0) { switch (err) { case EPERM: pcilib_error("Trying to unlock not locked mutex (%s) or the mutex which was locked by a different thread", lock->name); break; default: pcilib_error("Can't unlock mutex, errno %i", err); } } }