From 813d4cbe94b99e33ac2b921ae76df4d1b2b39b40 Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Sun, 11 Apr 2010 16:07:39 +0000 Subject: [PATCH] * Moved created subdirectory src/system/kernel/lock.cpp to new subdirectory locks. * Added syscalls for a new kind of mutex. A mutex consists only of an int32 and doesn't require any kernel resources. So it's initialization cannot fail (it consists only of setting the mutex value to 0). An uncontended lock or unlock operation can basically consist of an atomic_*() in userland. The syscalls (when the mutex is contended) are a bit more expensive than semaphore operations, though. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@36158 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- headers/private/kernel/user_mutex.h | 29 +++ headers/private/system/syscalls.h | 7 + headers/private/system/user_mutex_defs.h | 21 ++ src/system/kernel/Jamfile | 6 +- src/system/kernel/{ => locks}/lock.cpp | 0 src/system/kernel/locks/user_mutex.cpp | 302 +++++++++++++++++++++++ src/system/kernel/main.cpp | 4 + src/system/kernel/syscalls.cpp | 1 + 8 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 headers/private/kernel/user_mutex.h create mode 100644 headers/private/system/user_mutex_defs.h rename src/system/kernel/{ => locks}/lock.cpp (100%) create mode 100644 src/system/kernel/locks/user_mutex.cpp 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