| #include <stdio.h> |
| #include <string.h> |
| #include <sys/mman.h> |
| #include <assert.h> |
| #ifdef CONFIG_VALGRIND_DEV |
| #include <valgrind/valgrind.h> |
| #else |
| #define RUNNING_ON_VALGRIND 0 |
| #endif |
| |
| #include "fio_sem.h" |
| #include "pshared.h" |
| #include "os/os.h" |
| #include "fio_time.h" |
| #include "gettime.h" |
| |
| void __fio_sem_remove(struct fio_sem *sem) |
| { |
| assert(sem->magic == FIO_SEM_MAGIC); |
| pthread_mutex_destroy(&sem->lock); |
| pthread_cond_destroy(&sem->cond); |
| |
| /* |
| * When not running on Valgrind, ensure any subsequent attempt to grab |
| * this semaphore will fail with an assert, instead of just silently |
| * hanging. When running on Valgrind, let Valgrind detect |
| * use-after-free. |
| */ |
| if (!RUNNING_ON_VALGRIND) |
| memset(sem, 0, sizeof(*sem)); |
| } |
| |
| void fio_sem_remove(struct fio_sem *sem) |
| { |
| __fio_sem_remove(sem); |
| munmap((void *) sem, sizeof(*sem)); |
| } |
| |
| int __fio_sem_init(struct fio_sem *sem, int value) |
| { |
| int ret; |
| |
| sem->value = value; |
| /* Initialize .waiters explicitly for Valgrind. */ |
| sem->waiters = 0; |
| sem->magic = FIO_SEM_MAGIC; |
| |
| ret = mutex_cond_init_pshared(&sem->lock, &sem->cond); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| struct fio_sem *fio_sem_init(int value) |
| { |
| struct fio_sem *sem = NULL; |
| |
| sem = (void *) mmap(NULL, sizeof(struct fio_sem), |
| PROT_READ | PROT_WRITE, |
| OS_MAP_ANON | MAP_SHARED, -1, 0); |
| if (sem == MAP_FAILED) { |
| perror("mmap semaphore"); |
| return NULL; |
| } |
| |
| if (!__fio_sem_init(sem, value)) |
| return sem; |
| |
| fio_sem_remove(sem); |
| return NULL; |
| } |
| |
| static bool sem_timed_out(struct timespec *t, unsigned int msecs) |
| { |
| struct timeval tv; |
| struct timespec now; |
| |
| gettimeofday(&tv, NULL); |
| now.tv_sec = tv.tv_sec; |
| now.tv_nsec = tv.tv_usec * 1000; |
| |
| return mtime_since(t, &now) >= msecs; |
| } |
| |
| int fio_sem_down_timeout(struct fio_sem *sem, unsigned int msecs) |
| { |
| struct timeval tv_s; |
| struct timespec base; |
| struct timespec t; |
| int ret = 0; |
| |
| assert(sem->magic == FIO_SEM_MAGIC); |
| |
| gettimeofday(&tv_s, NULL); |
| base.tv_sec = t.tv_sec = tv_s.tv_sec; |
| base.tv_nsec = t.tv_nsec = tv_s.tv_usec * 1000; |
| |
| t.tv_sec += msecs / 1000; |
| t.tv_nsec += ((msecs * 1000000ULL) % 1000000000); |
| if (t.tv_nsec >= 1000000000) { |
| t.tv_nsec -= 1000000000; |
| t.tv_sec++; |
| } |
| |
| pthread_mutex_lock(&sem->lock); |
| |
| sem->waiters++; |
| while (!sem->value && !ret) { |
| /* |
| * Some platforms (FreeBSD 9?) seems to return timed out |
| * way too early, double check. |
| */ |
| ret = pthread_cond_timedwait(&sem->cond, &sem->lock, &t); |
| if (ret == ETIMEDOUT && !sem_timed_out(&base, msecs)) |
| ret = 0; |
| } |
| sem->waiters--; |
| |
| if (!ret) { |
| sem->value--; |
| pthread_mutex_unlock(&sem->lock); |
| return 0; |
| } |
| |
| pthread_mutex_unlock(&sem->lock); |
| return ret; |
| } |
| |
| bool fio_sem_down_trylock(struct fio_sem *sem) |
| { |
| bool ret = true; |
| |
| assert(sem->magic == FIO_SEM_MAGIC); |
| |
| pthread_mutex_lock(&sem->lock); |
| if (sem->value) { |
| sem->value--; |
| ret = false; |
| } |
| pthread_mutex_unlock(&sem->lock); |
| |
| return ret; |
| } |
| |
| void fio_sem_down(struct fio_sem *sem) |
| { |
| assert(sem->magic == FIO_SEM_MAGIC); |
| |
| pthread_mutex_lock(&sem->lock); |
| |
| while (!sem->value) { |
| sem->waiters++; |
| pthread_cond_wait(&sem->cond, &sem->lock); |
| sem->waiters--; |
| } |
| |
| sem->value--; |
| pthread_mutex_unlock(&sem->lock); |
| } |
| |
| void fio_sem_up(struct fio_sem *sem) |
| { |
| int do_wake = 0; |
| |
| assert(sem->magic == FIO_SEM_MAGIC); |
| |
| pthread_mutex_lock(&sem->lock); |
| read_barrier(); |
| if (!sem->value && sem->waiters) |
| do_wake = 1; |
| sem->value++; |
| |
| if (do_wake) |
| pthread_cond_signal(&sem->cond); |
| |
| pthread_mutex_unlock(&sem->lock); |
| } |