diff --git a/headers/private/kernel/user_mutex.h b/headers/private/kernel/user_mutex.h new file mode 100644 index 0000000000..a2eacd7437 --- /dev/null +++ b/headers/private/kernel/user_mutex.h @@ -0,0 +1,29 @@ +/* + * Copyright 2010, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef _KERNEL_USER_MUTEX_H +#define _KERNEL_USER_MUTEX_H + + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + +void user_mutex_init(); + +status_t _user_mutex_lock(int32* mutex, const char* name, uint32 flags, + bigtime_t timeout); +status_t _user_mutex_unlock(int32* mutex, uint32 flags); +status_t _user_mutex_switch_lock(int32* fromMutex, int32* toMutex, + const char* name, uint32 flags, bigtime_t timeout); + +#ifdef __cplusplus +} +#endif + + +#endif /* _KERNEL_USER_MUTEX_H */ diff --git a/headers/private/system/syscalls.h b/headers/private/system/syscalls.h index dbdc19f8f0..9829e9112f 100644 --- a/headers/private/system/syscalls.h +++ b/headers/private/system/syscalls.h @@ -71,6 +71,13 @@ extern status_t _kern_get_safemode_option(const char *parameter, extern ssize_t _kern_wait_for_objects(object_wait_info* infos, int numInfos, uint32 flags, bigtime_t timeout); +/* user mutex functions */ +extern status_t _kern_mutex_lock(int32* mutex, const char* name, + uint32 flags, bigtime_t timeout); +extern status_t _kern_mutex_unlock(int32* mutex, uint32 flags); +extern status_t _kern_mutex_switch_lock(int32* fromMutex, int32* toMutex, + const char* name, uint32 flags, bigtime_t timeout); + /* sem functions */ extern sem_id _kern_create_sem(int count, const char *name); extern status_t _kern_delete_sem(sem_id id); diff --git a/headers/private/system/user_mutex_defs.h b/headers/private/system/user_mutex_defs.h new file mode 100644 index 0000000000..ef000c8453 --- /dev/null +++ b/headers/private/system/user_mutex_defs.h @@ -0,0 +1,21 @@ +/* + * Copyright 2010, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef _SYSTEM_USER_MUTEX_DEFS_H +#define _SYSTEM_USER_MUTEX_DEFS_H + + +// user mutex specific flags passed to _kern_user_mutex_unlock() +#define B_USER_MUTEX_UNBLOCK_ALL 0x80000000 + // All threads currently waiting on the mutex will be unblocked. The mutex + // state will be locked. + + +// mutex value flags +#define B_USER_MUTEX_LOCKED 0x01 +#define B_USER_MUTEX_WAITING 0x02 +#define B_USER_MUTEX_DISABLED 0x04 + + +#endif /* _SYSTEM_USER_MUTEX_DEFS_H */ diff --git a/src/system/kernel/Jamfile b/src/system/kernel/Jamfile index 0aadc9d070..be743c5614 100644 --- a/src/system/kernel/Jamfile +++ b/src/system/kernel/Jamfile @@ -10,6 +10,7 @@ SubDir HAIKU_TOP src system kernel ; SubDirC++Flags $(defines) ; } +SEARCH_SOURCE += [ FDirName $(SUBDIR) locks ] ; SEARCH_SOURCE += [ FDirName $(SUBDIR) scheduler ] ; UsePrivateHeaders libroot ; @@ -34,7 +35,6 @@ KernelMergeObject kernel_core.o : kernel_daemon.cpp linkhack.c listeners.cpp - lock.cpp low_resource_manager.cpp main.cpp module.cpp @@ -53,6 +53,10 @@ KernelMergeObject kernel_core.o : usergroup.cpp wait_for_objects.cpp + # locks + lock.cpp + user_mutex.cpp + # scheduler scheduler.cpp scheduler_affine.cpp diff --git a/src/system/kernel/lock.cpp b/src/system/kernel/locks/lock.cpp similarity index 100% rename from src/system/kernel/lock.cpp rename to src/system/kernel/locks/lock.cpp diff --git a/src/system/kernel/locks/user_mutex.cpp b/src/system/kernel/locks/user_mutex.cpp new file mode 100644 index 0000000000..be1c3e3f58 --- /dev/null +++ b/src/system/kernel/locks/user_mutex.cpp @@ -0,0 +1,302 @@ +/* + * Copyright 2010, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +struct UserMutexEntry; +typedef DoublyLinkedList UserMutexEntryList; + +struct UserMutexEntry : public DoublyLinkedListLinkImpl { + 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 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.Head(); + if (firstEntry != NULL) { + firstEntry->otherEntries.MoveFrom(&entry->otherEntries); + sUserMutexTable.Insert(firstEntry); + return true; + } + + return false; +} + + +static status_t +user_mutex_lock_locked(vint32* 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); + + // The mutex might have been unlocked (or disabled) in the meantime. + 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; + } + + // we have to wait + + // 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(); + + // dequeue + if (!remove_user_mutex_entry(&entry)) { + // no one is waiting anymore -- clear the waiting flag + atomic_and(mutex, ~(int32)B_USER_MUTEX_WAITING); + } + + if (error != B_OK + && (entry.locked || (*mutex & B_USER_MUTEX_DISABLED) != 0)) { + // timeout or interrupt, but the mutex was unlocked or disabled in time + error = B_OK; + } + + return error; +} + + +static void +user_mutex_unlock_locked(vint32* mutex, addr_t physicalAddress, uint32 flags) +{ + if (UserMutexEntry* entry = sUserMutexTable.Lookup(physicalAddress)) { + // unblock the first thread + entry->locked = true; + entry->condition.NotifyOne(); + + if ((flags & B_USER_MUTEX_UNBLOCK_ALL) != 0 + && (*mutex & B_USER_MUTEX_DISABLED) != 0) { + // unblock all the other waiting threads as well + for (UserMutexEntryList::Iterator it + = entry->otherEntries.GetIterator(); + UserMutexEntry* otherEntry = it.Next();) { + otherEntry->locked = true; + otherEntry->condition.NotifyOne(); + } + } + } +} + + +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; + } + + syscall_restart_handle_timeout_pre(flags, timeout); + + status_t error = user_mutex_switch_lock(fromMutex, toMutex, name, + flags | B_CAN_INTERRUPT, timeout); + + return syscall_restart_handle_timeout_post(error, timeout); +} diff --git a/src/system/kernel/main.cpp b/src/system/kernel/main.cpp index 68757c6f66..c21bfd3ea6 100644 --- a/src/system/kernel/main.cpp +++ b/src/system/kernel/main.cpp @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -257,6 +258,9 @@ main2(void *unused) TRACE("init ports\n"); port_init(&sKernelArgs); + TRACE("init user mutex\n"); + user_mutex_init(); + TRACE("Init modules\n"); boot_splash_set_stage(BOOT_SPLASH_STAGE_1_INIT_MODULES); module_init_post_threads(); diff --git a/src/system/kernel/syscalls.cpp b/src/system/kernel/syscalls.cpp index 013be84edc..1084c85804 100644 --- a/src/system/kernel/syscalls.cpp +++ b/src/system/kernel/syscalls.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include