* Fixes sharing semantics, so non-shared semaphores in non-shared memory do not become shared after a fork. * Adds two new system calls: _user_mutex_sem_acquire/release(), which reuse the user_mutex address-hashed wait mechanism. * Named semaphores continue to use traditional sem_id semaphores.
431 lines
10 KiB
C++
431 lines
10 KiB
C++
/*
|
|
* Copyright 2015, Hamish Morrison, [email protected].
|
|
* Copyright 2010, Ingo Weinhold, [email protected].
|
|
* Distributed under the terms of the MIT License.
|
|
*/
|
|
|
|
|
|
#include <user_mutex.h>
|
|
#include <user_mutex_defs.h>
|
|
|
|
#include <condition_variable.h>
|
|
#include <kernel.h>
|
|
#include <lock.h>
|
|
#include <smp.h>
|
|
#include <syscall_restart.h>
|
|
#include <util/AutoLock.h>
|
|
#include <util/OpenHashTable.h>
|
|
#include <vm/vm.h>
|
|
#include <vm/VMArea.h>
|
|
|
|
|
|
struct UserMutexEntry;
|
|
typedef DoublyLinkedList<UserMutexEntry> UserMutexEntryList;
|
|
|
|
struct UserMutexEntry : public DoublyLinkedListLinkImpl<UserMutexEntry> {
|
|
addr_t address;
|
|
ConditionVariable condition;
|
|
bool locked;
|
|
UserMutexEntryList otherEntries;
|
|
UserMutexEntry* hashNext;
|
|
};
|
|
|
|
struct UserMutexHashDefinition {
|
|
typedef addr_t KeyType;
|
|
typedef UserMutexEntry ValueType;
|
|
|
|
size_t HashKey(addr_t key) const
|
|
{
|
|
return key >> 2;
|
|
}
|
|
|
|
size_t Hash(const UserMutexEntry* value) const
|
|
{
|
|
return HashKey(value->address);
|
|
}
|
|
|
|
bool Compare(addr_t key, const UserMutexEntry* value) const
|
|
{
|
|
return value->address == key;
|
|
}
|
|
|
|
UserMutexEntry*& GetLink(UserMutexEntry* value) const
|
|
{
|
|
return value->hashNext;
|
|
}
|
|
};
|
|
|
|
typedef BOpenHashTable<UserMutexHashDefinition> UserMutexTable;
|
|
|
|
|
|
static UserMutexTable sUserMutexTable;
|
|
static mutex sUserMutexTableLock = MUTEX_INITIALIZER("user mutex table");
|
|
|
|
|
|
static void
|
|
add_user_mutex_entry(UserMutexEntry* entry)
|
|
{
|
|
UserMutexEntry* firstEntry = sUserMutexTable.Lookup(entry->address);
|
|
if (firstEntry != NULL)
|
|
firstEntry->otherEntries.Add(entry);
|
|
else
|
|
sUserMutexTable.Insert(entry);
|
|
}
|
|
|
|
|
|
static bool
|
|
remove_user_mutex_entry(UserMutexEntry* entry)
|
|
{
|
|
UserMutexEntry* firstEntry = sUserMutexTable.Lookup(entry->address);
|
|
if (firstEntry != entry) {
|
|
// The entry is not the first entry in the table. Just remove it from
|
|
// the first entry's list.
|
|
firstEntry->otherEntries.Remove(entry);
|
|
return true;
|
|
}
|
|
|
|
// The entry is the first entry in the table. Remove it from the table and,
|
|
// if any, add the next entry to the table.
|
|
sUserMutexTable.Remove(entry);
|
|
|
|
firstEntry = entry->otherEntries.RemoveHead();
|
|
if (firstEntry != NULL) {
|
|
firstEntry->otherEntries.MoveFrom(&entry->otherEntries);
|
|
sUserMutexTable.Insert(firstEntry);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static status_t
|
|
user_mutex_wait_locked(int32* mutex, addr_t physicalAddress, const char* name,
|
|
uint32 flags, bigtime_t timeout, MutexLocker& locker, bool& lastWaiter)
|
|
{
|
|
// add the entry to the table
|
|
UserMutexEntry entry;
|
|
entry.address = physicalAddress;
|
|
entry.locked = false;
|
|
add_user_mutex_entry(&entry);
|
|
|
|
// wait
|
|
ConditionVariableEntry waitEntry;
|
|
entry.condition.Init((void*)physicalAddress, "user mutex");
|
|
entry.condition.Add(&waitEntry);
|
|
|
|
locker.Unlock();
|
|
status_t error = waitEntry.Wait(flags, timeout);
|
|
locker.Lock();
|
|
|
|
if (error != B_OK && entry.locked)
|
|
error = B_OK;
|
|
|
|
if (!entry.locked) {
|
|
// if nobody woke us up, we have to dequeue ourselves
|
|
lastWaiter = !remove_user_mutex_entry(&entry);
|
|
} else {
|
|
// otherwise the waker has done the work of marking the
|
|
// mutex or semaphore uncontended
|
|
lastWaiter = false;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
static status_t
|
|
user_mutex_lock_locked(int32* mutex, addr_t physicalAddress,
|
|
const char* name, uint32 flags, bigtime_t timeout, MutexLocker& locker)
|
|
{
|
|
// mark the mutex locked + waiting
|
|
int32 oldValue = atomic_or(mutex,
|
|
B_USER_MUTEX_LOCKED | B_USER_MUTEX_WAITING);
|
|
|
|
if ((oldValue & (B_USER_MUTEX_LOCKED | B_USER_MUTEX_WAITING)) == 0
|
|
|| (oldValue & B_USER_MUTEX_DISABLED) != 0) {
|
|
// clear the waiting flag and be done
|
|
atomic_and(mutex, ~(int32)B_USER_MUTEX_WAITING);
|
|
return B_OK;
|
|
}
|
|
|
|
bool lastWaiter;
|
|
status_t error = user_mutex_wait_locked(mutex, physicalAddress, name,
|
|
flags, timeout, locker, lastWaiter);
|
|
|
|
if (lastWaiter)
|
|
atomic_and(mutex, ~(int32)B_USER_MUTEX_WAITING);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
static void
|
|
user_mutex_unlock_locked(int32* mutex, addr_t physicalAddress, uint32 flags)
|
|
{
|
|
UserMutexEntry* entry = sUserMutexTable.Lookup(physicalAddress);
|
|
if (entry == NULL) {
|
|
// no one is waiting -- clear locked flag
|
|
atomic_and(mutex, ~(int32)B_USER_MUTEX_LOCKED);
|
|
return;
|
|
}
|
|
|
|
// Someone is waiting -- set the locked flag. It might still be set,
|
|
// but when using userland atomic operations, the caller will usually
|
|
// have cleared it already.
|
|
int32 oldValue = atomic_or(mutex, B_USER_MUTEX_LOCKED);
|
|
|
|
// unblock the first thread
|
|
entry->locked = true;
|
|
entry->condition.NotifyOne();
|
|
|
|
if ((flags & B_USER_MUTEX_UNBLOCK_ALL) != 0
|
|
|| (oldValue & B_USER_MUTEX_DISABLED) != 0) {
|
|
// unblock and dequeue all the other waiting threads as well
|
|
while (UserMutexEntry* otherEntry = entry->otherEntries.RemoveHead()) {
|
|
otherEntry->locked = true;
|
|
otherEntry->condition.NotifyOne();
|
|
}
|
|
|
|
// dequeue the first thread and mark the mutex uncontended
|
|
sUserMutexTable.Remove(entry);
|
|
atomic_and(mutex, ~(int32)B_USER_MUTEX_WAITING);
|
|
} else {
|
|
bool otherWaiters = remove_user_mutex_entry(entry);
|
|
if (!otherWaiters)
|
|
atomic_and(mutex, ~(int32)B_USER_MUTEX_WAITING);
|
|
}
|
|
}
|
|
|
|
|
|
static status_t
|
|
user_mutex_sem_acquire_locked(int32* sem, addr_t physicalAddress,
|
|
const char* name, uint32 flags, bigtime_t timeout, MutexLocker& locker)
|
|
{
|
|
// The semaphore may have been released in the meantime, and we also
|
|
// need to mark it as contended if it isn't already.
|
|
int32 oldValue = atomic_get(sem);
|
|
while (oldValue > -1) {
|
|
int32 value = atomic_test_and_set(sem, oldValue - 1, oldValue);
|
|
if (value == oldValue && value > 0)
|
|
return B_OK;
|
|
oldValue = value;
|
|
}
|
|
|
|
bool lastWaiter;
|
|
status_t error = user_mutex_wait_locked(sem, physicalAddress, name, flags,
|
|
timeout, locker, lastWaiter);
|
|
|
|
if (lastWaiter)
|
|
atomic_test_and_set(sem, 0, -1);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
static void
|
|
user_mutex_sem_release_locked(int32* sem, addr_t physicalAddress)
|
|
{
|
|
UserMutexEntry* entry = sUserMutexTable.Lookup(physicalAddress);
|
|
if (!entry) {
|
|
// no waiters - mark as uncontended and release
|
|
int32 oldValue = atomic_get(sem);
|
|
while (true) {
|
|
int32 inc = oldValue < 0 ? 2 : 1;
|
|
int32 value = atomic_test_and_set(sem, oldValue + inc, oldValue);
|
|
if (value == oldValue)
|
|
return;
|
|
oldValue = value;
|
|
}
|
|
}
|
|
|
|
bool otherWaiters = remove_user_mutex_entry(entry);
|
|
|
|
entry->locked = true;
|
|
entry->condition.NotifyOne();
|
|
|
|
if (!otherWaiters) {
|
|
// mark the semaphore uncontended
|
|
atomic_test_and_set(sem, 0, -1);
|
|
}
|
|
}
|
|
|
|
|
|
static status_t
|
|
user_mutex_lock(int32* mutex, const char* name, uint32 flags, bigtime_t timeout)
|
|
{
|
|
// wire the page and get the physical address
|
|
VMPageWiringInfo wiringInfo;
|
|
status_t error = vm_wire_page(B_CURRENT_TEAM, (addr_t)mutex, true,
|
|
&wiringInfo);
|
|
if (error != B_OK)
|
|
return error;
|
|
|
|
// get the lock
|
|
{
|
|
MutexLocker locker(sUserMutexTableLock);
|
|
error = user_mutex_lock_locked(mutex, wiringInfo.physicalAddress, name,
|
|
flags, timeout, locker);
|
|
}
|
|
|
|
// unwire the page
|
|
vm_unwire_page(&wiringInfo);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
static status_t
|
|
user_mutex_switch_lock(int32* fromMutex, int32* toMutex, const char* name,
|
|
uint32 flags, bigtime_t timeout)
|
|
{
|
|
// wire the pages and get the physical addresses
|
|
VMPageWiringInfo fromWiringInfo;
|
|
status_t error = vm_wire_page(B_CURRENT_TEAM, (addr_t)fromMutex, true,
|
|
&fromWiringInfo);
|
|
if (error != B_OK)
|
|
return error;
|
|
|
|
VMPageWiringInfo toWiringInfo;
|
|
error = vm_wire_page(B_CURRENT_TEAM, (addr_t)toMutex, true, &toWiringInfo);
|
|
if (error != B_OK) {
|
|
vm_unwire_page(&fromWiringInfo);
|
|
return error;
|
|
}
|
|
|
|
// unlock the first mutex and lock the second one
|
|
{
|
|
MutexLocker locker(sUserMutexTableLock);
|
|
user_mutex_unlock_locked(fromMutex, fromWiringInfo.physicalAddress,
|
|
flags);
|
|
|
|
error = user_mutex_lock_locked(toMutex, toWiringInfo.physicalAddress,
|
|
name, flags, timeout, locker);
|
|
}
|
|
|
|
// unwire the pages
|
|
vm_unwire_page(&toWiringInfo);
|
|
vm_unwire_page(&fromWiringInfo);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
// #pragma mark - kernel private
|
|
|
|
|
|
void
|
|
user_mutex_init()
|
|
{
|
|
if (sUserMutexTable.Init() != B_OK)
|
|
panic("user_mutex_init(): Failed to init table!");
|
|
}
|
|
|
|
|
|
// #pragma mark - syscalls
|
|
|
|
|
|
status_t
|
|
_user_mutex_lock(int32* mutex, const char* name, uint32 flags,
|
|
bigtime_t timeout)
|
|
{
|
|
if (mutex == NULL || !IS_USER_ADDRESS(mutex) || (addr_t)mutex % 4 != 0)
|
|
return B_BAD_ADDRESS;
|
|
|
|
syscall_restart_handle_timeout_pre(flags, timeout);
|
|
|
|
status_t error = user_mutex_lock(mutex, name, flags | B_CAN_INTERRUPT,
|
|
timeout);
|
|
|
|
return syscall_restart_handle_timeout_post(error, timeout);
|
|
}
|
|
|
|
|
|
status_t
|
|
_user_mutex_unlock(int32* mutex, uint32 flags)
|
|
{
|
|
if (mutex == NULL || !IS_USER_ADDRESS(mutex) || (addr_t)mutex % 4 != 0)
|
|
return B_BAD_ADDRESS;
|
|
|
|
// wire the page and get the physical address
|
|
VMPageWiringInfo wiringInfo;
|
|
status_t error = vm_wire_page(B_CURRENT_TEAM, (addr_t)mutex, true,
|
|
&wiringInfo);
|
|
if (error != B_OK)
|
|
return error;
|
|
|
|
{
|
|
MutexLocker locker(sUserMutexTableLock);
|
|
user_mutex_unlock_locked(mutex, wiringInfo.physicalAddress, flags);
|
|
}
|
|
|
|
vm_unwire_page(&wiringInfo);
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
_user_mutex_switch_lock(int32* fromMutex, int32* toMutex, const char* name,
|
|
uint32 flags, bigtime_t timeout)
|
|
{
|
|
if (fromMutex == NULL || !IS_USER_ADDRESS(fromMutex)
|
|
|| (addr_t)fromMutex % 4 != 0 || toMutex == NULL
|
|
|| !IS_USER_ADDRESS(toMutex) || (addr_t)toMutex % 4 != 0) {
|
|
return B_BAD_ADDRESS;
|
|
}
|
|
|
|
return user_mutex_switch_lock(fromMutex, toMutex, name,
|
|
flags | B_CAN_INTERRUPT, timeout);
|
|
}
|
|
|
|
|
|
status_t
|
|
_user_mutex_sem_acquire(int32* sem, const char* name, uint32 flags,
|
|
bigtime_t timeout)
|
|
{
|
|
if (sem == NULL || !IS_USER_ADDRESS(sem) || (addr_t)sem % 4 != 0)
|
|
return B_BAD_ADDRESS;
|
|
|
|
syscall_restart_handle_timeout_pre(flags, timeout);
|
|
|
|
// wire the page and get the physical address
|
|
VMPageWiringInfo wiringInfo;
|
|
status_t error = vm_wire_page(B_CURRENT_TEAM, (addr_t)sem, true,
|
|
&wiringInfo);
|
|
if (error != B_OK)
|
|
return error;
|
|
|
|
{
|
|
MutexLocker locker(sUserMutexTableLock);
|
|
error = user_mutex_sem_acquire_locked(sem, wiringInfo.physicalAddress,
|
|
name, flags | B_CAN_INTERRUPT, timeout, locker);
|
|
}
|
|
|
|
vm_unwire_page(&wiringInfo);
|
|
return syscall_restart_handle_timeout_post(error, timeout);
|
|
}
|
|
|
|
|
|
status_t
|
|
_user_mutex_sem_release(int32* sem)
|
|
{
|
|
if (sem == NULL || !IS_USER_ADDRESS(sem) || (addr_t)sem % 4 != 0)
|
|
return B_BAD_ADDRESS;
|
|
|
|
// wire the page and get the physical address
|
|
VMPageWiringInfo wiringInfo;
|
|
status_t error = vm_wire_page(B_CURRENT_TEAM, (addr_t)sem, true,
|
|
&wiringInfo);
|
|
if (error != B_OK)
|
|
return error;
|
|
|
|
{
|
|
MutexLocker locker(sUserMutexTableLock);
|
|
user_mutex_sem_release_locked(sem, wiringInfo.physicalAddress);
|
|
}
|
|
|
|
vm_unwire_page(&wiringInfo);
|
|
return B_OK;
|
|
}
|