pthread: implement reader/writer lock
parent
542a2e5d9d
commit
10d36df795
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* @ingroup pthread
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
/**
|
||||
* @brief Attributes for a new reader/writer lock.
|
||||
* @details The options set in this struct will be ignored by pthread_rwlock_init().
|
||||
*/
|
||||
typedef struct pthread_rwlockattr
|
||||
{
|
||||
/**
|
||||
* @brief Whether to share lock with child processes.
|
||||
* @details Valid values are `PTHREAD_PROCESS_SHARED` and `PTHREAD_PROCESS_PRIVATE`.
|
||||
* Since RIOT is a single-process operating system, this value is ignored.
|
||||
*/
|
||||
int pshared;
|
||||
} pthread_rwlockattr_t;
|
||||
|
||||
/**
|
||||
* @brief Initilize the attribute set with the defaults.
|
||||
* @details Default value for pshared: `PTHREAD_PROCESS_PRIVATE`.
|
||||
* A zeroed out datum is initialized.
|
||||
* @param[in,out] attr Attribute set to initialize.
|
||||
* @returns `0` on success.
|
||||
* `EINVAL` if `attr == NULL`.
|
||||
*/
|
||||
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
|
||||
|
||||
/**
|
||||
* @brief Destroy an attribute set.
|
||||
* @details This function does nothing, don't bother calling it.
|
||||
* @param[in,out] attr Attribute set to destroy.
|
||||
* @returns `0` on success.
|
||||
* `EINVAL` if `attr == NULL`.
|
||||
*/
|
||||
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
|
||||
|
||||
/**
|
||||
* @brief Read whether to share the lock with child processes.
|
||||
* @details There are not child processes in RIOT.
|
||||
* @param[in] attr Attribute set to query.
|
||||
* @param[out] pshared Either `PTHREAD_PROCESS_SHARED` or `PTHREAD_PROCESS_PRIVATE`.
|
||||
* @returns `0` on success.
|
||||
* `EINVAL` if `attr == NULL`.
|
||||
*/
|
||||
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int *pshared);
|
||||
|
||||
/**
|
||||
* @brief Set whether to share the lock with child processes.
|
||||
* @details There are not child processes in RIOT.
|
||||
* @param[in,out] attr Attribute set to operate on.
|
||||
* @param[in] pshared Either `PTHREAD_PROCESS_SHARED` or `PTHREAD_PROCESS_PRIVATE`.
|
||||
* @returns `0` on success.
|
||||
* `EINVAL` if `attr == NULL` or a wrong value for `pshared` was supplied.
|
||||
*/
|
||||
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);
|
@ -0,0 +1,290 @@
|
||||
/*
|
||||
* Copyright (C) 2014 René Kijewski <rene.kijewski@fu-berlin.de>
|
||||
*
|
||||
* 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.1 of the License, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup pthread
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Implementation of a fair, POSIX conforming reader/writer lock.
|
||||
*
|
||||
* @author René Kijewski <rene.kijewski@fu-berlin.de>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include "pthread.h"
|
||||
#include "sched.h"
|
||||
#include "vtimer.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr)
|
||||
{
|
||||
(void) attr;
|
||||
|
||||
if (rwlock == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
memset(rwlock, 0, sizeof (*rwlock));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)
|
||||
{
|
||||
if (rwlock == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
/* do not unlock the mutex, no need */
|
||||
if ((mutex_trylock(&rwlock->mutex) == 0) || (rwlock->readers != 0)) {
|
||||
return EBUSY;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool __pthread_rwlock_blocked_readingly(const pthread_rwlock_t *rwlock)
|
||||
{
|
||||
if (rwlock->readers < 0) {
|
||||
/* a writer holds the lock */
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Determine if there is a writer waiting to get this lock who has a higher or the same priority: */
|
||||
|
||||
if (rwlock->queue.next == NULL) {
|
||||
/* no waiting thread */
|
||||
return false;
|
||||
}
|
||||
|
||||
queue_node_t *qnode = rwlock->queue.next;
|
||||
if (qnode->priority > active_thread->priority) {
|
||||
/* the waiting thread has a lower priority */
|
||||
return false;
|
||||
}
|
||||
|
||||
/* if the waiting node is a writer, then we cannot enter the critical section (to prevent starving the writer) */
|
||||
__pthread_rwlock_waiter_node_t *waiting_node = (__pthread_rwlock_waiter_node_t *) qnode->data;
|
||||
return waiting_node->is_writer;
|
||||
}
|
||||
|
||||
bool __pthread_rwlock_blocked_writingly(const pthread_rwlock_t *rwlock)
|
||||
{
|
||||
/* if any thread holds the lock, then no writer may enter the critical section */
|
||||
return rwlock->readers != 0;
|
||||
}
|
||||
|
||||
static int pthread_rwlock_lock(pthread_rwlock_t *rwlock,
|
||||
bool (*is_blocked)(const pthread_rwlock_t *rwlock),
|
||||
bool is_writer,
|
||||
int incr_when_held,
|
||||
bool allow_spurious)
|
||||
{
|
||||
if (rwlock == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&rwlock->mutex);
|
||||
if (!is_blocked(rwlock)) {
|
||||
rwlock->readers += incr_when_held;
|
||||
}
|
||||
else {
|
||||
/* queue for the lock */
|
||||
__pthread_rwlock_waiter_node_t waiting_node = {
|
||||
.is_writer = is_writer,
|
||||
.thread = (tcb_t *) active_thread,
|
||||
.qnode = {
|
||||
.next = NULL,
|
||||
.data = (uintptr_t) &waiting_node,
|
||||
.priority = active_thread->priority,
|
||||
},
|
||||
.continue_ = false,
|
||||
};
|
||||
queue_priority_add(&rwlock->queue, &waiting_node.qnode);
|
||||
|
||||
while (1) {
|
||||
/* wait to be unlocked, so this thread can try to acquire the lock again */
|
||||
mutex_unlock_and_sleep(&rwlock->mutex);
|
||||
|
||||
mutex_lock(&rwlock->mutex);
|
||||
if (waiting_node.continue_) {
|
||||
/* pthread_rwlock_unlock() already set rwlock->readers */
|
||||
break;
|
||||
}
|
||||
else if (allow_spurious) {
|
||||
queue_remove(&rwlock->queue, &waiting_node.qnode);
|
||||
mutex_unlock(&rwlock->mutex);
|
||||
return ETIMEDOUT;
|
||||
}
|
||||
}
|
||||
}
|
||||
mutex_unlock(&rwlock->mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pthread_rwlock_trylock(pthread_rwlock_t *rwlock,
|
||||
bool (*is_blocked)(const pthread_rwlock_t *rwlock),
|
||||
int incr_when_held)
|
||||
{
|
||||
if (rwlock == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
else if (mutex_trylock(&rwlock->mutex) == 0) {
|
||||
return EBUSY;
|
||||
}
|
||||
else if (is_blocked(rwlock)) {
|
||||
mutex_unlock(&rwlock->mutex);
|
||||
return EBUSY;
|
||||
}
|
||||
|
||||
rwlock->readers += incr_when_held;
|
||||
|
||||
mutex_unlock(&rwlock->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pthread_rwlock_timedlock(pthread_rwlock_t *rwlock,
|
||||
bool (*is_blocked)(const pthread_rwlock_t *rwlock),
|
||||
bool is_writer,
|
||||
int incr_when_held,
|
||||
const struct timespec *abstime)
|
||||
{
|
||||
timex_t now, then;
|
||||
|
||||
then.seconds = abstime->tv_sec;
|
||||
then.microseconds = abstime->tv_nsec / 1000u;
|
||||
timex_normalize(&then);
|
||||
|
||||
vtimer_now(&now);
|
||||
|
||||
if (timex_cmp(then, now) <= 0) {
|
||||
return ETIMEDOUT;
|
||||
}
|
||||
else {
|
||||
timex_t reltime = timex_sub(then, now);
|
||||
|
||||
vtimer_t timer;
|
||||
vtimer_set_wakeup(&timer, reltime, active_thread->pid);
|
||||
int result = pthread_rwlock_lock(rwlock, is_blocked, is_writer, incr_when_held, true);
|
||||
if (result != ETIMEDOUT) {
|
||||
vtimer_remove(&timer);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)
|
||||
{
|
||||
return pthread_rwlock_lock(rwlock, __pthread_rwlock_blocked_readingly, false, +1, false);
|
||||
}
|
||||
|
||||
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock)
|
||||
{
|
||||
return pthread_rwlock_lock(rwlock, __pthread_rwlock_blocked_writingly, true, -1, false);
|
||||
}
|
||||
|
||||
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock)
|
||||
{
|
||||
return pthread_rwlock_trylock(rwlock, __pthread_rwlock_blocked_readingly, +1);
|
||||
}
|
||||
|
||||
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock)
|
||||
{
|
||||
return pthread_rwlock_trylock(rwlock, __pthread_rwlock_blocked_writingly, -1);
|
||||
}
|
||||
|
||||
int pthread_rwlock_timedrdlock(pthread_rwlock_t *rwlock, const struct timespec *abstime)
|
||||
{
|
||||
return pthread_rwlock_timedlock(rwlock, __pthread_rwlock_blocked_readingly, false, +1, abstime);
|
||||
}
|
||||
|
||||
int pthread_rwlock_timedwrlock(pthread_rwlock_t *rwlock, const struct timespec *abstime)
|
||||
{
|
||||
return pthread_rwlock_timedlock(rwlock, __pthread_rwlock_blocked_writingly, true, -1, abstime);
|
||||
}
|
||||
|
||||
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)
|
||||
{
|
||||
if (rwlock == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&rwlock->mutex);
|
||||
if (rwlock->readers == 0) {
|
||||
/* the lock is open */
|
||||
mutex_unlock(&rwlock->mutex);
|
||||
return EPERM;
|
||||
}
|
||||
|
||||
if (rwlock->readers > 0) {
|
||||
--rwlock->readers;
|
||||
}
|
||||
else {
|
||||
rwlock->readers = 0;
|
||||
}
|
||||
|
||||
if (rwlock->readers != 0 || rwlock->queue.next == NULL) {
|
||||
/* this thread was not the last reader, or no one is waiting to aquire the lock */
|
||||
mutex_unlock(&rwlock->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* wake up the next thread */
|
||||
queue_node_t *qnode = queue_remove_head(&rwlock->queue);
|
||||
__pthread_rwlock_waiter_node_t *waiting_node = (__pthread_rwlock_waiter_node_t *) qnode->data;
|
||||
waiting_node->continue_ = true;
|
||||
uint16_t prio = qnode->priority;
|
||||
sched_set_status(waiting_node->thread, STATUS_PENDING);
|
||||
|
||||
if (waiting_node->is_writer) {
|
||||
--rwlock->readers;
|
||||
}
|
||||
else {
|
||||
++rwlock->readers;
|
||||
|
||||
/* wake up further readers */
|
||||
while (rwlock->queue.next) {
|
||||
waiting_node = (__pthread_rwlock_waiter_node_t *) rwlock->queue.next->data;
|
||||
if (waiting_node->is_writer) {
|
||||
/* Not to be unfair to writers, we don't try to wake up readers that came after the first writer. */
|
||||
break;
|
||||
}
|
||||
waiting_node->continue_ = true;
|
||||
|
||||
/* wake up this reader */
|
||||
qnode = queue_remove_head(&rwlock->queue);
|
||||
if (qnode->priority < prio) {
|
||||
prio = qnode->priority;
|
||||
}
|
||||
sched_set_status(waiting_node->thread, STATUS_PENDING);
|
||||
|
||||
++rwlock->readers;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&rwlock->mutex);
|
||||
|
||||
/* yield if a woken up thread had a higher priority */
|
||||
sched_switch(active_thread->priority, prio);
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2014 René Kijewski <rene.kijewski@fu-berlin.de>
|
||||
*
|
||||
* 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.1 of the License, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup pthread
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Implementation of a fair, POSIX conforming reader/writer lock (attribute set).
|
||||
*
|
||||
* @author René Kijewski <rene.kijewski@fu-berlin.de>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include "pthread.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr)
|
||||
{
|
||||
if (attr == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
memset(attr, 0, sizeof (*attr));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr)
|
||||
{
|
||||
if (attr == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
(void) attr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return current setting of process-shared attribute of ATTR in PSHARED. */
|
||||
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int *pshared)
|
||||
{
|
||||
if (attr == NULL || pshared == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
*pshared = attr->pshared;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared)
|
||||
{
|
||||
if (attr == NULL || (pshared != PTHREAD_PROCESS_SHARED && pshared != PTHREAD_PROCESS_PRIVATE)) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
attr->pshared = pshared;
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue