From 8562499f441ac23a80dd89310dbf2016b681fef9 Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Thu, 1 May 2008 01:53:07 +0000 Subject: [PATCH] * Introduced a new locking primitive I called "cutex" (sorry for the name, couldn't resist :-P). It's semantically equivalent to a mutex, but doesn't need a semaphore (it uses thread blocking and a simple queue instead). Initialization can't fail. In fact it is ready to use without initialization when living in the bss segment, also in the early boot process. It's as fast as a benaphore in cases of low lock contention, and faster otherwise. Only disadvantage is the higher immediate memory footprint of 16 bytes. * Changed how the "thread" and "threads" debugger commands list the objects they are waiting for. Cutexes are also included. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@25276 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- headers/private/kernel/lock.h | 64 +++++++++ headers/private/kernel/thread_types.h | 1 + headers/private/kernel/util/AutoLock.h | 19 +++ src/system/kernel/Jamfile | 2 +- src/system/kernel/{lock.c => lock.cpp} | 183 ++++++++++++++++++++++++- src/system/kernel/main.c | 2 + src/system/kernel/thread.cpp | 105 ++++++++++---- 7 files changed, 347 insertions(+), 29 deletions(-) rename src/system/kernel/{lock.c => lock.cpp} (56%) diff --git a/headers/private/kernel/lock.h b/headers/private/kernel/lock.h index efd770b270..e5d9d186c3 100644 --- a/headers/private/kernel/lock.h +++ b/headers/private/kernel/lock.h @@ -39,13 +39,29 @@ typedef struct rw_lock { #define RW_MAX_READERS 1000000 +struct cutex_waiter; + +typedef struct cutex { + const char* name; + struct cutex_waiter* waiters; +#ifdef KDEBUG + thread_id holder; +#else + int32 count; +#endif + int32 release_count; +} cutex; + + #if 0 && KDEBUG // XXX disable this for now, it causes problems when including thread.h here # include #define ASSERT_LOCKED_RECURSIVE(r) { ASSERT(thread_get_current_thread_id() == (r)->holder); } #define ASSERT_LOCKED_MUTEX(m) { ASSERT(thread_get_current_thread_id() == (m)->holder); } +#define ASSERT_LOCKED_CUTEX(m) { ASSERT(thread_get_current_thread_id() == (m)->holder); } #else #define ASSERT_LOCKED_RECURSIVE(r) #define ASSERT_LOCKED_MUTEX(m) +#define ASSERT_LOCKED_CUTEX(m) #endif @@ -103,6 +119,54 @@ extern status_t rw_lock_read_unlock(rw_lock *lock); extern status_t rw_lock_write_lock(rw_lock *lock); extern status_t rw_lock_write_unlock(rw_lock *lock); +extern void cutex_init(cutex* lock, const char *name); + // name is *not* cloned nor freed in cutex_destroy() +extern void cutex_destroy(cutex* lock); + +// implementation private: +extern void _cutex_lock(cutex* lock); +extern void _cutex_unlock(cutex* lock); +extern status_t _cutex_trylock(cutex* lock); + + +static inline void +cutex_lock(cutex* lock) +{ +#ifdef KDEBUG + _cutex_lock(lock); +#else + if (atomic_add(&lock->count, -1) < 0) + _cutex_lock(lock); +#endif +} + + +static inline status_t +cutex_trylock(cutex* lock) +{ +#ifdef KDEBUG + return _cutex_trylock(lock); +#else + if (atomic_test_and_set(&lock->count, -1, 0) != 0) + return B_WOULD_BLOCK; +#endif +} + + +static inline void +cutex_unlock(cutex* lock) +{ +#ifdef KDEBUG + _cutex_unlock(lock); +#else + if (atomic_add(&lock->count, 1) < -1) + _cutex_unlock(lock); +#endif +} + + +extern void lock_debug_init(); + #ifdef __cplusplus } #endif diff --git a/headers/private/kernel/thread_types.h b/headers/private/kernel/thread_types.h index ac3240e783..f6a774250d 100644 --- a/headers/private/kernel/thread_types.h +++ b/headers/private/kernel/thread_types.h @@ -62,6 +62,7 @@ enum { THREAD_BLOCK_TYPE_CONDITION_VARIABLE = 1, THREAD_BLOCK_TYPE_SNOOZE = 2, THREAD_BLOCK_TYPE_SIGNAL = 3, + THREAD_BLOCK_TYPE_CUTEX = 4, THREAD_BLOCK_TYPE_OTHER = 9999, THREAD_BLOCK_TYPE_USER_BASE = 10000 diff --git a/headers/private/kernel/util/AutoLock.h b/headers/private/kernel/util/AutoLock.h index e51cd88509..96339d2e30 100644 --- a/headers/private/kernel/util/AutoLock.h +++ b/headers/private/kernel/util/AutoLock.h @@ -67,6 +67,24 @@ public: // BenaphoreLocker typedef AutoLocker BenaphoreLocker; +// CutexLocking +class CutexLocking { +public: + inline bool Lock(cutex *lockable) + { + cutex_lock(lockable); + return true; + } + + inline void Unlock(cutex *lockable) + { + cutex_unlock(lockable); + } +}; + +// CutexLocker +typedef AutoLocker CutexLocker; + // InterruptsLocking class InterruptsLocking { public: @@ -153,6 +171,7 @@ using BPrivate::AutoLocker; using BPrivate::MutexLocker; using BPrivate::RecursiveLocker; using BPrivate::BenaphoreLocker; +using BPrivate::CutexLocker; using BPrivate::InterruptsLocker; using BPrivate::SpinLocker; using BPrivate::InterruptsSpinLocker; diff --git a/src/system/kernel/Jamfile b/src/system/kernel/Jamfile index 15d41e8fc6..bc311f0fd1 100644 --- a/src/system/kernel/Jamfile +++ b/src/system/kernel/Jamfile @@ -27,7 +27,7 @@ KernelMergeObject kernel_core.o : int.c kernel_daemon.c linkhack.c - lock.c + lock.cpp main.c module.cpp Notifications.cpp diff --git a/src/system/kernel/lock.c b/src/system/kernel/lock.cpp similarity index 56% rename from src/system/kernel/lock.c rename to src/system/kernel/lock.cpp index 158a995342..cbce4119ad 100644 --- a/src/system/kernel/lock.c +++ b/src/system/kernel/lock.cpp @@ -1,4 +1,5 @@ /* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. * Copyright 2002-2008, Axel Dörfler, axeld@pinc-software.de. All rights reserved. * Distributed under the terms of the MIT License. * @@ -8,14 +9,22 @@ /* Mutex and recursive_lock code */ +#include #include -#include -#include -#include #include +#include +#include #include +#include + + +struct cutex_waiter { + struct thread* thread; + cutex_waiter* next; // next in queue + cutex_waiter* last; // last in queue (valid for the first in queue) +}; int32 @@ -279,3 +288,171 @@ rw_lock_write_unlock(rw_lock *lock) return release_sem_etc(lock->sem, RW_MAX_READERS, 0); } + +// #pragma mark - + + +void +cutex_init(cutex* lock, const char *name) +{ + lock->name = name; + lock->waiters = NULL; +#ifdef KDEBUG + lock->holder = -1; +#else + lock->count = 0; +#endif + lock->release_count = 0; +} + + +void +cutex_destroy(cutex* lock) +{ + // no-op +} + + +void +_cutex_lock(cutex* lock) +{ +#ifdef KDEBUG + if (!kernel_startup && !are_interrupts_enabled()) { + panic("_cutex_unlock: called with interrupts disabled for lock %p", + lock); + } +#endif + + InterruptsSpinLocker _(thread_spinlock); + + // Might have been released after we decremented the count, but before + // we acquired the spinlock. +#ifdef KDEBUG + if (lock->release_count >= 0) { + lock->holder = thread_get_current_thread_id(); +#else + if (lock->release_count > 0) { +#endif + lock->release_count--; + return; + } + + // enqueue in waiter list + cutex_waiter waiter; + waiter.thread = thread_get_current_thread(); + waiter.next = NULL; + + if (lock->waiters != NULL) { + lock->waiters->last->next = &waiter; + } else + lock->waiters = &waiter; + + lock->waiters->last = &waiter; + + // block + thread_prepare_to_block(waiter.thread, 0, THREAD_BLOCK_TYPE_CUTEX, lock); + thread_block_locked(waiter.thread); + +#ifdef KDEBUG + lock->holder = waiter.thread->id; +#endif +} + + +void +_cutex_unlock(cutex* lock) +{ + InterruptsSpinLocker _(thread_spinlock); + +#ifdef KDEBUG + if (thread_get_current_thread_id() != lock->holder) { + panic("_cutex_unlock() failure: thread %ld is trying to release " + "cutex %p (current holder %ld)\n", thread_get_current_thread_id(), + lock, lock->holder); + return; + } + + lock->holder = -1; +#endif + + cutex_waiter* waiter = lock->waiters; + if (waiter != NULL) { + // dequeue the first waiter + lock->waiters = waiter->next; + if (lock->waiters != NULL) + lock->waiters->last = waiter->last; + + // unblock thread + thread_unblock_locked(waiter->thread, B_OK); + } else { + // We acquired the spinlock before the locker that is going to wait. + // Just increment the release count. + lock->release_count++; + } +} + + +status_t +_cutex_trylock(cutex* lock) +{ +#ifdef KDEBUG + InterruptsSpinLocker _(thread_spinlock); + + if (lock->release_count >= 0) { + lock->holder = thread_get_current_thread_id(); + lock->release_count--; + return B_OK; + } +#endif + return B_WOULD_BLOCK; +} + + +static int +dump_cutex_info(int argc, char** argv) +{ + if (argc < 2) { + print_debugger_command_usage(argv[0]); + return 0; + } + + cutex* lock = (cutex*)strtoul(argv[1], NULL, 0); + + if (!IS_KERNEL_ADDRESS(lock)) { + kprintf("invalid address: %p\n", lock); + return 0; + } + + kprintf("cutex %p:\n", lock); + kprintf(" name: %s\n", lock->name); + kprintf(" release count: %ld\n", lock->release_count); +#ifdef KDEBUG + kprintf(" holder: %ld\n", lock->holder); +#else + kprintf(" count: %ld\n", lock->count); +#endif + + kprintf(" waiting threads:"); + cutex_waiter* waiter = lock->waiters; + while (waiter != NULL) { + kprintf(" %ld", waiter->thread->id); + waiter = waiter->next; + } + kputs("\n"); + + return 0; +} + + +// #pragma mark - + + +void +lock_debug_init() +{ + add_debugger_command_etc("cutex", &dump_cutex_info, + "Dump info about a cutex", + "\n" + "Prints info about the specified cutex.\n" + " - pointer to the cutex to print the info for.\n", 0); +} diff --git a/src/system/kernel/main.c b/src/system/kernel/main.c index 5c1212debf..4170cfe1ac 100644 --- a/src/system/kernel/main.c +++ b/src/system/kernel/main.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -117,6 +118,7 @@ _start(kernel_args *bootKernelArgs, int currentCPU) // now we can use the heap and create areas arch_platform_init_post_vm(&sKernelArgs); + lock_debug_init(); TRACE("init driver_settings\n"); boot_item_init(); driver_settings_init(&sKernelArgs); diff --git a/src/system/kernel/thread.cpp b/src/system/kernel/thread.cpp index ad397dba2c..303b8e603f 100644 --- a/src/system/kernel/thread.cpp +++ b/src/system/kernel/thread.cpp @@ -667,17 +667,6 @@ get_thread_wait_sem(struct thread* thread) } -static ConditionVariable* -get_thread_wait_cvar(struct thread* thread) -{ - if (thread->state == B_THREAD_WAITING - && thread->wait.type == THREAD_BLOCK_TYPE_CONDITION_VARIABLE) { - return (ConditionVariable*)thread->wait.object; - } - return NULL; -} - - /*! Fills the thread_info structure with information from the specified thread. @@ -1091,10 +1080,6 @@ state_to_text(struct thread *thread, int32 state) return "receive"; break; } - - case THREAD_BLOCK_TYPE_CONDITION_VARIABLE: - default: - break; } return "waiting"; @@ -1133,8 +1118,47 @@ _dump_thread_info(struct thread *thread) kprintf("sig_pending: %#lx (blocked: %#lx)\n", thread->sig_pending, thread->sig_block_mask); kprintf("in_kernel: %d\n", thread->in_kernel); - kprintf("sem blocking: %ld\n", get_thread_wait_sem(thread)); - kprintf("condition variable: %p\n", get_thread_wait_cvar(thread)); + + kprintf("waiting for: "); + + if (thread->state == B_THREAD_WAITING) { + switch (thread->wait.type) { + case THREAD_BLOCK_TYPE_SEMAPHORE: + { + sem_id sem = (sem_id)(addr_t)thread->wait.object; + if (sem == thread->msg.read_sem) + kprintf("data\n"); + else + kprintf("semaphore %ld\n", sem); + break; + } + + case THREAD_BLOCK_TYPE_CONDITION_VARIABLE: + kprintf("condition variable %p\n", thread->wait.object); + break; + + case THREAD_BLOCK_TYPE_SNOOZE: + kprintf("snooze()\n"); + break; + + case THREAD_BLOCK_TYPE_SIGNAL: + kprintf("signal\n"); + break; + + case THREAD_BLOCK_TYPE_CUTEX: + kprintf("cutex %p\n", thread->wait.object); + break; + + case THREAD_BLOCK_TYPE_OTHER: + kprintf("other (%s)\n", (char*)thread->wait.object); + break; + + default: + kprintf("unknown (%p)\n", thread->wait.object); + break; + } + } + kprintf("fault_handler: %p\n", (void *)thread->fault_handler); kprintf("args: %p %p\n", thread->args1, thread->args2); kprintf("entry: %p\n", (void *)thread->entry); @@ -1254,8 +1278,8 @@ dump_thread_list(int argc, char **argv) kprintf("ignoring invalid team argument.\n"); } - kprintf("thread id state sem/cv cpu pri stack team " - "name\n"); + kprintf("thread id state wait for object cpu pri stack " + " team name\n"); hash_open(sThreadHash, &i); while ((thread = (struct thread*)hash_next(sThreadHash, &i)) != NULL) { @@ -1268,17 +1292,48 @@ dump_thread_list(int argc, char **argv) || (realTimeOnly && thread->priority < B_REAL_TIME_DISPLAY_PRIORITY)) continue; - kprintf("%p %6ld %-9s", thread, thread->id, state_to_text(thread, + kprintf("%p %6ld %-10s", thread, thread->id, state_to_text(thread, thread->state)); // does it block on a semaphore or a condition variable? if (thread->state == B_THREAD_WAITING) { - if (get_thread_wait_cvar(thread)) - kprintf("%p ", get_thread_wait_cvar(thread)); - else - kprintf("%10ld ", get_thread_wait_sem(thread)); + switch (thread->wait.type) { + case THREAD_BLOCK_TYPE_SEMAPHORE: + { + sem_id sem = (sem_id)(addr_t)thread->wait.object; + if (sem == thread->msg.read_sem) + kprintf(" "); + else + kprintf("sem %12ld ", sem); + break; + } + + case THREAD_BLOCK_TYPE_CONDITION_VARIABLE: + kprintf("cvar %p ", thread->wait.object); + break; + + case THREAD_BLOCK_TYPE_SNOOZE: + kprintf(" "); + break; + + case THREAD_BLOCK_TYPE_SIGNAL: + kprintf("signal "); + break; + + case THREAD_BLOCK_TYPE_CUTEX: + kprintf("cutex %p ", thread->wait.object); + break; + + case THREAD_BLOCK_TYPE_OTHER: + kprintf("other "); + break; + + default: + kprintf("??? %p ", thread->wait.object); + break; + } } else - kprintf(" - "); + kprintf(" - "); // on which CPU does it run? if (thread->cpu)