From 203c622934560505b1872079b625c008cf7bc973 Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Fri, 27 Feb 2009 16:17:58 +0000 Subject: [PATCH] * Added condition variables. * Added a dummy slab implementation. * Updated the lock, hash, and block cache implementations. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@29336 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- .../file_systems/userlandfs/server/Jamfile | 7 +- .../userlandfs/server/UserlandFSServer.cpp | 8 +- .../userlandfs/server/haiku_block_cache.cpp | 1996 ++++++++++++++--- .../userlandfs/server/haiku_block_cache.h | 22 + .../server/haiku_block_cache_priv.h | 93 - .../server/haiku_condition_variable.cpp | 263 +++ .../server/haiku_condition_variable.h | 101 + .../userlandfs/server/haiku_fs_cache.h | 55 +- .../userlandfs/server/haiku_hash.cpp | 198 +- .../userlandfs/server/haiku_hash.h | 22 +- .../userlandfs/server/haiku_lock.cpp | 133 +- .../userlandfs/server/haiku_lock.h | 116 +- .../userlandfs/server/haiku_slab.h | 62 + 13 files changed, 2509 insertions(+), 567 deletions(-) create mode 100644 src/add-ons/kernel/file_systems/userlandfs/server/haiku_block_cache.h delete mode 100644 src/add-ons/kernel/file_systems/userlandfs/server/haiku_block_cache_priv.h create mode 100644 src/add-ons/kernel/file_systems/userlandfs/server/haiku_condition_variable.cpp create mode 100644 src/add-ons/kernel/file_systems/userlandfs/server/haiku_condition_variable.h create mode 100644 src/add-ons/kernel/file_systems/userlandfs/server/haiku_slab.h diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/Jamfile b/src/add-ons/kernel/file_systems/userlandfs/server/Jamfile index b186ca55f0..e432646307 100644 --- a/src/add-ons/kernel/file_systems/userlandfs/server/Jamfile +++ b/src/add-ons/kernel/file_systems/userlandfs/server/Jamfile @@ -8,6 +8,9 @@ SubDirSysHdrs [ FDirName $(userlandFSIncludes) ] ; SubDirHdrs [ FDirName $(userlandFSIncludes) private ] ; SubDirHdrs [ FDirName $(userlandFSIncludes) shared ] ; +UsePrivateSystemHeaders ; +UsePrivateHeaders libroot ; + SEARCH_SOURCE += [ FDirName $(userlandFSTop) private ] ; SEARCH_SOURCE += [ FDirName $(userlandFSTop) shared ] ; @@ -40,10 +43,12 @@ Application UserlandFSServer # BeOSKernelVolume.cpp DispatcherFileSystem.cpp FileSystem.cpp -# haiku_block_cache.cpp + haiku_block_cache.cpp + haiku_condition_variable.cpp # haiku_file_cache.cpp haiku_hash.cpp haiku_lock.cpp + haiku_slab.cpp HaikuKernelFileSystem.cpp HaikuKernelVolume.cpp kernel_emu.cpp diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/UserlandFSServer.cpp b/src/add-ons/kernel/file_systems/userlandfs/server/UserlandFSServer.cpp index b395019bc2..19d5b5d51b 100644 --- a/src/add-ons/kernel/file_systems/userlandfs/server/UserlandFSServer.cpp +++ b/src/add-ons/kernel/file_systems/userlandfs/server/UserlandFSServer.cpp @@ -23,7 +23,8 @@ #include "DispatcherDefs.h" #include "FileSystem.h" #include "FSInfo.h" -#include "haiku_block_cache_priv.h" +#include "haiku_block_cache.h" +#include "haiku_condition_variable.h" #include "haiku_fs_cache.h" #include "HaikuKernelFileSystem.h" #include "RequestThread.h" @@ -277,6 +278,11 @@ UserlandFSServer::_CreateHaikuKernelInterface(const char* fsName, RETURN_ERROR(B_NO_MEMORY); ObjectDeleter fsDeleter(fileSystem); + // init condition variables + error = UserlandFS::HaikuKernelEmu::condition_variable_init(); + if (error != B_OK) + RETURN_ERROR(error); + // init block cache error = UserlandFS::HaikuKernelEmu::block_cache_init(); if (error != B_OK) diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/haiku_block_cache.cpp b/src/add-ons/kernel/file_systems/userlandfs/server/haiku_block_cache.cpp index eb4f5b9f06..8d868336e4 100644 --- a/src/add-ons/kernel/file_systems/userlandfs/server/haiku_block_cache.cpp +++ b/src/add-ons/kernel/file_systems/userlandfs/server/haiku_block_cache.cpp @@ -1,9 +1,9 @@ /* - * Copyright 2004-2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Copyright 2004-2008, Axel Dörfler, axeld@pinc-software.de. * Distributed under the terms of the MIT License. */ -#include "haiku_block_cache_priv.h" +#include "haiku_block_cache.h" #include @@ -12,90 +12,685 @@ #include #include +#include + +#include + +#include "haiku_condition_variable.h" #include "haiku_fs_cache.h" #include "haiku_hash.h" +#include "haiku_lock.h" +#include "haiku_slab.h" #include "kernel_emu.h" + // TODO: this is a naive but growing implementation to test the API: // 1) block reading/writing is not at all optimized for speed, it will // just read and write single blocks. // 2) the locking could be improved; getting a block should not need to // wait for blocks to be written -// 3) dirty blocks are only written back if asked for // TODO: the retrieval/copy of the original data could be delayed until the // new data must be written, ie. in low memory situations. + //#define TRACE_BLOCK_CACHE +#undef TRACE /* undef TRACE from */ #ifdef TRACE_BLOCK_CACHE # define TRACE(x) dprintf x #else # define TRACE(x) ; #endif -using std::nothrow; - // This macro is used for fatal situations that are acceptable in a running // system, like out of memory situations - should only panic for debugging. #define FATAL(x) panic x + using UserlandFS::KernelEmu::dprintf; using UserlandFS::KernelEmu::dump_block; using UserlandFS::KernelEmu::panic; +using UserlandFS::KernelEmu::spawn_kernel_thread; namespace UserlandFS { namespace HaikuKernelEmu { -static const int32 kMaxBlockCount = 1024; +static const bigtime_t kTransactionIdleTime = 2000000LL; + // a transaction is considered idle after 2 seconds of inactivity + + +struct cache_transaction; +struct cached_block; +struct block_cache; +typedef DoublyLinkedListLink block_link; + +struct cached_block { + cached_block* next; // next in hash + cached_block* transaction_next; + block_link link; + off_t block_number; + void* current_data; + void* original_data; + void* parent_data; +#if BLOCK_CACHE_DEBUG_CHANGED + void* compare; +#endif + int32 ref_count; + int32 accessed; + bool busy : 1; + bool is_writing : 1; + bool is_dirty : 1; + bool unused : 1; + bool discard : 1; + cache_transaction* transaction; + cache_transaction* previous_transaction; + + static int Compare(void* _cacheEntry, const void* _block); + static uint32 Hash(void* _cacheEntry, const void* _block, uint32 range); +}; + +typedef DoublyLinkedList > block_list; + +struct cache_notification : DoublyLinkedListLinkImpl { + int32 transaction_id; + int32 events_pending; + int32 events; + transaction_notification_hook hook; + void* data; + bool delete_after_event; +}; + +typedef DoublyLinkedList NotificationList; + +struct block_cache : DoublyLinkedListLinkImpl { + hash_table* hash; + mutex lock; + int fd; + off_t max_blocks; + size_t block_size; + int32 next_transaction_id; + cache_transaction* last_transaction; + hash_table* transaction_hash; + + object_cache* buffer_cache; + block_list unused_blocks; + + uint32 num_dirty_blocks; + bool read_only; + + NotificationList pending_notifications; + ConditionVariable condition_variable; + + block_cache(int fd, off_t numBlocks, size_t blockSize, + bool readOnly); + ~block_cache(); + + status_t Init(); + + void Free(void* buffer); + void* Allocate(); + void RemoveBlock(cached_block* block); + void DiscardBlock(cached_block* block); + void FreeBlock(cached_block* block); + cached_block* NewBlock(off_t blockNumber); + + +private: + cached_block* _GetUnusedBlock(); +}; + +struct cache_listener; +typedef DoublyLinkedListLink listener_link; + +struct cache_listener : cache_notification { + listener_link link; +}; + +typedef DoublyLinkedList > ListenerList; struct cache_transaction { cache_transaction(); - cache_transaction *next; + cache_transaction* next; int32 id; int32 num_blocks; + int32 main_num_blocks; int32 sub_num_blocks; - cached_block *first_block; + cached_block* first_block; block_list blocks; - transaction_notification_hook notification_hook; - void *notification_data; + ListenerList listeners; bool open; bool has_sub_transaction; + bigtime_t last_used; }; -static status_t write_cached_block(block_cache *cache, cached_block *block, +#if BLOCK_CACHE_BLOCK_TRACING +namespace BlockTracing { + +class Action : public AbstractTraceEntry { +public: + Action(block_cache* cache, cached_block* block) + : + fCache(cache), + fBlockNumber(block->block_number), + fIsDirty(block->is_dirty), + fHasOriginal(block->original_data != NULL), + fHasParent(block->parent_data != NULL), + fTransactionID(-1), + fPreviousID(-1) + { + if (block->transaction != NULL) + fTransactionID = block->transaction->id; + if (block->previous_transaction != NULL) + fPreviousID = block->previous_transaction->id; + } + + virtual void AddDump(TraceOutput& out) + { + out.Print("block cache %p, %s %Ld, %c%c%c transaction %ld " + "(previous id %ld)\n", fCache, _Action(), fBlockNumber, + fIsDirty ? 'd' : '-', fHasOriginal ? 'o' : '-', + fHasParent ? 'p' : '-', fTransactionID, fPreviousID); + } + + virtual const char* _Action() const = 0; + +private: + block_cache* fCache; + uint64 fBlockNumber; + bool fIsDirty; + bool fHasOriginal; + bool fHasParent; + int32 fTransactionID; + int32 fPreviousID; +}; + +class Get : public Action { +public: + Get(block_cache* cache, cached_block* block) + : Action(cache, block) + { + Initialized(); + } + + virtual const char* _Action() const { return "get"; } +}; + +class Put : public Action { +public: + Put(block_cache* cache, cached_block* block) + : Action(cache, block) + { + Initialized(); + } + + virtual const char* _Action() const { return "put"; } +}; + +class Read : public Action { +public: + Read(block_cache* cache, cached_block* block) + : Action(cache, block) + { + Initialized(); + } + + virtual const char* _Action() const { return "read"; } +}; + +class Write : public Action { +public: + Write(block_cache* cache, cached_block* block) + : Action(cache, block) + { + Initialized(); + } + + virtual const char* _Action() const { return "write"; } +}; + +class Flush : public Action { +public: + Flush(block_cache* cache, cached_block* block, bool getUnused = false) + : Action(cache, block), + fGetUnused(getUnused) + { + Initialized(); + } + + virtual const char* _Action() const + { return fGetUnused ? "get-unused" : "flush"; } + +private: + bool fGetUnused; +}; + +class Error : public AbstractTraceEntry { +public: + Error(block_cache* cache, uint64 blockNumber, const char* message, + status_t status = B_OK) + : + fCache(cache), + fBlockNumber(blockNumber), + fMessage(message), + fStatus(status) + { + Initialized(); + } + + virtual void AddDump(TraceOutput& out) + { + out.Print("block cache %p, error %Ld, %s%s%s", + fCache, fBlockNumber, fMessage, fStatus != B_OK ? ": " : "", + fStatus != B_OK ? strerror(fStatus) : ""); + } + +private: + block_cache* fCache; + uint64 fBlockNumber; + const char* fMessage; + status_t fStatus; +}; + +} // namespace BlockTracing + +# define TB(x) new(std::nothrow) BlockTracing::x; +#else +# define TB(x) ; +#endif + +#if BLOCK_CACHE_TRANSACTION_TRACING +namespace TransactionTracing { + +class Action : public AbstractTraceEntry { + public: + Action(const char* label, block_cache* cache, + cache_transaction* transaction) + : + fCache(cache), + fTransaction(transaction), + fID(transaction->id), + fSub(transaction->has_sub_transaction), + fNumBlocks(transaction->num_blocks), + fSubNumBlocks(transaction->sub_num_blocks) + { + strlcpy(fLabel, label, sizeof(fLabel)); + Initialized(); + } + + virtual void AddDump(TraceOutput& out) + { + out.Print("block cache %p, %s transaction %p (id %ld)%s" + ", %ld/%ld blocks", fCache, fLabel, fTransaction, fID, + fSub ? " sub" : "", fNumBlocks, fSubNumBlocks); + } + + private: + char fLabel[12]; + block_cache *fCache; + cache_transaction *fTransaction; + int32 fID; + bool fSub; + int32 fNumBlocks; + int32 fSubNumBlocks; +}; + +class Detach : public AbstractTraceEntry { + public: + Detach(block_cache* cache, cache_transaction* transaction, + cache_transaction* newTransaction) + : + fCache(cache), + fTransaction(transaction), + fID(transaction->id), + fSub(transaction->has_sub_transaction), + fNewTransaction(newTransaction), + fNewID(newTransaction->id) + { + Initialized(); + } + + virtual void AddDump(TraceOutput& out) + { + out.Print("block cache %p, detach transaction %p (id %ld)" + "from transaction %p (id %ld)%s", + fCache, fNewTransaction, fNewID, fTransaction, fID, + fSub ? " sub" : ""); + } + + private: + block_cache *fCache; + cache_transaction *fTransaction; + int32 fID; + bool fSub; + cache_transaction *fNewTransaction; + int32 fNewID; +}; + +class Abort : public AbstractTraceEntry { + public: + Abort(block_cache* cache, cache_transaction* transaction) + : + fCache(cache), + fTransaction(transaction), + fID(transaction->id), + fNumBlocks(0) + { + bool isSub = transaction->has_sub_transaction; + fNumBlocks = isSub ? transaction->sub_num_blocks + : transaction->num_blocks; + fBlocks = (off_t*)alloc_tracing_buffer(fNumBlocks * sizeof(off_t)); + if (fBlocks != NULL) { + cached_block* block = transaction->first_block; + for (int32 i = 0; block != NULL && i < fNumBlocks; + block = block->transaction_next) { + fBlocks[i++] = block->block_number; + } + } else + fNumBlocks = 0; + Initialized(); + } + + virtual void AddDump(TraceOutput& out) + { + out.Print("block cache %p, abort transaction " + "%p (id %ld), blocks", fCache, fTransaction, fID); + for (int32 i = 0; i < fNumBlocks && !out.IsFull(); i++) + out.Print(" %Ld", fBlocks[i]); + } + + private: + block_cache *fCache; + cache_transaction *fTransaction; + int32 fID; + off_t *fBlocks; + int32 fNumBlocks; +}; + +} // namespace TransactionTracing + +# define T(x) new(std::nothrow) TransactionTracing::x; +#else +# define T(x) ; +#endif + + +static status_t write_cached_block(block_cache* cache, cached_block* block, bool deleteTransaction = true); +static DoublyLinkedList sCaches; +static mutex sCachesLock = MUTEX_INITIALIZER("block caches"); +static sem_id sEventSemaphore; +static mutex sNotificationsLock = MUTEX_INITIALIZER("block cache notifications"); +static thread_id sNotifierWriterThread; +static DoublyLinkedListLink sMarkCache; + // TODO: this only works if the link is the first entry of block_cache +static object_cache* sBlockCache; + + +// #pragma mark - notifications/listener + + +/*! Checks wether or not this is an event that closes a transaction. */ +static inline bool +is_closing_event(int32 event) +{ + return (event & (TRANSACTION_ABORTED | TRANSACTION_ENDED)) != 0; +} + + +static inline bool +is_written_event(int32 event) +{ + return (event & TRANSACTION_WRITTEN) != 0; +} + + +/*! From the specified \a notification, it will remove the lowest pending + event, and return that one in \a _event. + If there is no pending event anymore, it will return \c false. +*/ +static bool +get_next_pending_event(cache_notification* notification, int32* _event) +{ + for (int32 eventMask = 1; eventMask <= TRANSACTION_IDLE; eventMask <<= 1) { + int32 pending = atomic_and(¬ification->events_pending, + ~eventMask); + + bool more = (pending & ~eventMask) != 0; + + if ((pending & eventMask) != 0) { + *_event = eventMask; + return more; + } + } + + return false; +} + + +static void +flush_pending_notifications(block_cache* cache) +{ + ASSERT_LOCKED_MUTEX(&sCachesLock); + + while (true) { + MutexLocker locker(sNotificationsLock); + + cache_notification* notification = cache->pending_notifications.Head(); + if (notification == NULL) + return; + + bool deleteAfterEvent = false; + int32 event = -1; + if (!get_next_pending_event(notification, &event)) { + // remove the notification if this was the last pending event + cache->pending_notifications.Remove(notification); + deleteAfterEvent = notification->delete_after_event; + } + + if (event >= 0) { + // Notify listener, we need to copy the notification, as it might + // be removed when we unlock the list. + cache_notification copy = *notification; + locker.Unlock(); + + copy.hook(copy.transaction_id, event, copy.data); + + locker.Lock(); + } + + if (deleteAfterEvent) + delete notification; + } +} + + +/*! Flushes all pending notifications by calling the appropriate hook + functions. + Must not be called with a cache lock held. +*/ +static void +flush_pending_notifications() +{ + MutexLocker _(sCachesLock); + + DoublyLinkedList::Iterator iterator = sCaches.GetIterator(); + while (iterator.HasNext()) { + block_cache* cache = iterator.Next(); + + flush_pending_notifications(cache); + } +} + + +/*! Initializes the \a notification as specified. */ +static void +set_notification(cache_transaction* transaction, + cache_notification ¬ification, int32 events, + transaction_notification_hook hook, void* data) +{ + notification.transaction_id = transaction != NULL ? transaction->id : -1; + notification.events_pending = 0; + notification.events = events; + notification.hook = hook; + notification.data = data; + notification.delete_after_event = false; +} + + +/*! Makes sure the notification is deleted. It either deletes it directly, + when possible, or marks it for deletion if the notification is pending. +*/ +static void +delete_notification(cache_notification* notification) +{ + MutexLocker locker(sNotificationsLock); + + if (notification->events_pending != 0) + notification->delete_after_event = true; + else + delete notification; +} + + +/*! Adds the notification to the pending notifications list, or, if it's + already part of it, updates its events_pending field. + Also marks the notification to be deleted if \a deleteNotification + is \c true. + Triggers the notifier thread to run. +*/ +static void +add_notification(block_cache* cache, cache_notification* notification, + int32 event, bool deleteNotification) +{ + if (notification->hook == NULL) + return; + + int32 pending = atomic_or(¬ification->events_pending, event); + if (pending == 0) { + // not yet part of the notification list + MutexLocker locker(sNotificationsLock); + if (deleteNotification) + notification->delete_after_event = true; + cache->pending_notifications.Add(notification); + } else if (deleteNotification) { + // we might need to delete it ourselves if we're late + delete_notification(notification); + } + + release_sem_etc(sEventSemaphore, 1, B_DO_NOT_RESCHEDULE); + // We're probably still holding some locks that makes rescheduling + // not a good idea at this point. +} + + +/*! Notifies all interested listeners of this transaction about the \a event. + If \a event is a closing event (ie. TRANSACTION_ENDED, and + TRANSACTION_ABORTED), all listeners except those listening to + TRANSACTION_WRITTEN will be removed. +*/ +static void +notify_transaction_listeners(block_cache* cache, cache_transaction* transaction, + int32 event) +{ + T(Action("notify", cache, transaction)); + + bool isClosing = is_closing_event(event); + bool isWritten = is_written_event(event); + + ListenerList::Iterator iterator = transaction->listeners.GetIterator(); + while (iterator.HasNext()) { + cache_listener* listener = iterator.Next(); + + bool remove = (isClosing && !is_written_event(listener->events)) + || (isWritten && is_written_event(listener->events)); + if (remove) + iterator.Remove(); + + if ((listener->events & event) != 0) + add_notification(cache, listener, event, remove); + else if (remove) + delete_notification(listener); + } +} + + +/*! Removes and deletes all listeners that are still monitoring this + transaction. +*/ +static void +remove_transaction_listeners(block_cache* cache, cache_transaction* transaction) +{ + ListenerList::Iterator iterator = transaction->listeners.GetIterator(); + while (iterator.HasNext()) { + cache_listener* listener = iterator.Next(); + iterator.Remove(); + + delete_notification(listener); + } +} + + +static status_t +add_transaction_listener(block_cache* cache, cache_transaction* transaction, + int32 events, transaction_notification_hook hookFunction, void* data) +{ + ListenerList::Iterator iterator = transaction->listeners.GetIterator(); + while (iterator.HasNext()) { + cache_listener* listener = iterator.Next(); + + if (listener->data == data && listener->hook == hookFunction) { + // this listener already exists, just update it + listener->events |= events; + return B_OK; + } + } + + cache_listener* listener = new(std::nothrow) cache_listener; + if (listener == NULL) + return B_NO_MEMORY; + + set_notification(transaction, *listener, events, hookFunction, data); + transaction->listeners.Add(listener); + return B_OK; +} + + // #pragma mark - private transaction cache_transaction::cache_transaction() { num_blocks = 0; + main_num_blocks = 0; sub_num_blocks = 0; first_block = NULL; - notification_hook = NULL; - notification_data = NULL; open = true; + last_used = system_time(); } static int -transaction_compare(void *_transaction, const void *_id) +transaction_compare(void* _transaction, const void* _id) { - cache_transaction *transaction = (cache_transaction *)_transaction; - const int32 *id = (const int32 *)_id; + cache_transaction* transaction = (cache_transaction*)_transaction; + const int32* id = (const int32*)_id; return transaction->id - *id; } static uint32 -transaction_hash(void *_transaction, const void *_id, uint32 range) +transaction_hash(void* _transaction, const void* _id, uint32 range) { - cache_transaction *transaction = (cache_transaction *)_transaction; - const int32 *id = (const int32 *)_id; + cache_transaction* transaction = (cache_transaction*)_transaction; + const int32* id = (const int32*)_id; if (transaction != NULL) return transaction->id % range; @@ -105,43 +700,60 @@ transaction_hash(void *_transaction, const void *_id, uint32 range) static void -delete_transaction(block_cache *cache, cache_transaction *transaction) +delete_transaction(block_cache* cache, cache_transaction* transaction) { if (cache->last_transaction == transaction) cache->last_transaction = NULL; + remove_transaction_listeners(cache, transaction); delete transaction; } -static cache_transaction * -lookup_transaction(block_cache *cache, int32 id) +static cache_transaction* +lookup_transaction(block_cache* cache, int32 id) { - return (cache_transaction *)hash_lookup(cache->transaction_hash, &id); + return (cache_transaction*)hash_lookup(cache->transaction_hash, &id); } // #pragma mark - cached_block -/* static */ int -cached_block::Compare(void *_cacheEntry, const void *_block) +compare_blocks(const void* _blockA, const void* _blockB) { - cached_block *cacheEntry = (cached_block *)_cacheEntry; - const off_t *block = (const off_t *)_block; + cached_block* blockA = *(cached_block**)_blockA; + cached_block* blockB = *(cached_block**)_blockB; - return cacheEntry->block_number - *block; + off_t diff = blockA->block_number - blockB->block_number; + if (diff > 0) + return 1; + + return diff < 0 ? -1 : 0; +} + + +/*static*/ int +cached_block::Compare(void* _cacheEntry, const void* _block) +{ + cached_block* cacheEntry = (cached_block*)_cacheEntry; + const off_t* block = (const off_t*)_block; + + off_t diff = cacheEntry->block_number - *block; + if (diff > 0) + return 1; + + return diff < 0 ? -1 : 0; } -/* static */ -uint32 -cached_block::Hash(void *_cacheEntry, const void *_block, uint32 range) +/*static*/ uint32 +cached_block::Hash(void* _cacheEntry, const void* _block, uint32 range) { - cached_block *cacheEntry = (cached_block *)_cacheEntry; - const off_t *block = (const off_t *)_block; + cached_block* cacheEntry = (cached_block*)_cacheEntry; + const off_t* block = (const off_t*)_block; if (cacheEntry != NULL) return cacheEntry->block_number % range; @@ -154,48 +766,55 @@ cached_block::Hash(void *_cacheEntry, const void *_block, uint32 range) block_cache::block_cache(int _fd, off_t numBlocks, size_t blockSize, - bool readOnly) + bool readOnly) : hash(NULL), fd(_fd), max_blocks(numBlocks), block_size(blockSize), - allocated_block_count(0), next_transaction_id(1), last_transaction(NULL), transaction_hash(NULL), + buffer_cache(NULL), + num_dirty_blocks(0), read_only(readOnly) { - hash = hash_init(32, 0, &cached_block::Compare, &cached_block::Hash); - if (hash == NULL) - return; - - transaction_hash = hash_init(16, 0, &transaction_compare, - &UserlandFS::HaikuKernelEmu::transaction_hash); - if (transaction_hash == NULL) - return; - - if (benaphore_init(&lock, "block cache") < B_OK) - return; } +/*! Should be called with the cache's lock held. */ block_cache::~block_cache() { - benaphore_destroy(&lock); - hash_uninit(transaction_hash); hash_uninit(hash); + + delete_object_cache(buffer_cache); + + mutex_destroy(&lock); } status_t -block_cache::InitCheck() +block_cache::Init() { - if (lock.sem < B_OK) - return lock.sem; + condition_variable.Init(this, "cache transaction sync"); + mutex_init(&lock, "block cache"); - if (hash == NULL || transaction_hash == NULL) + buffer_cache = create_object_cache_etc("block cache buffers", block_size, + 8, 0, CACHE_LARGE_SLAB, NULL, NULL, NULL, NULL); + if (buffer_cache == NULL) + return B_NO_MEMORY; + + cached_block dummyBlock; + hash = hash_init(1024, offset_of_member(dummyBlock, next), + &cached_block::Compare, &cached_block::Hash); + if (hash == NULL) + return B_NO_MEMORY; + + cache_transaction dummyTransaction; + transaction_hash = hash_init(16, offset_of_member(dummyTransaction, next), + &transaction_compare, &UserlandFS::HaikuKernelEmu::transaction_hash); + if (transaction_hash == NULL) return B_NO_MEMORY; return B_OK; @@ -203,61 +822,60 @@ block_cache::InitCheck() void -block_cache::Free(void *address) +block_cache::Free(void* buffer) { - if (address == NULL) - return; - - free(address); + if (buffer != NULL) + object_cache_free(buffer_cache, buffer); } -void * +void* block_cache::Allocate() { - return malloc(block_size); + return object_cache_alloc(buffer_cache, 0); } void -block_cache::FreeBlock(cached_block *block) +block_cache::FreeBlock(cached_block* block) { Free(block->current_data); - block->current_data = NULL; if (block->original_data != NULL || block->parent_data != NULL) { - panic("block_cache::FreeBlock(): %p, %p\n", block->original_data, - block->parent_data); + panic("block_cache::FreeBlock(): %Ld, original %p, parent %p\n", + block->block_number, block->original_data, block->parent_data); } -#ifdef DEBUG_CHANGED +#if BLOCK_CACHE_DEBUG_CHANGED Free(block->compare); #endif - delete block; + object_cache_free(sBlockCache, block); } /*! Allocates a new block for \a blockNumber, ready for use */ -cached_block * +cached_block* block_cache::NewBlock(off_t blockNumber) { - cached_block *block = new(nothrow) cached_block; + cached_block* block = (cached_block*)object_cache_alloc(sBlockCache, 0); if (block == NULL) { - FATAL(("could not allocate block!\n")); - return NULL; - } + TB(Error(this, blockNumber, "allocation failed")); + dprintf("block allocation failed, unused list is %sempty.\n", + unused_blocks.IsEmpty() ? "" : "not "); - // if we hit the limit of blocks to cache¸ try to free one or more - if (allocated_block_count >= kMaxBlockCount) { - RemoveUnusedBlocks(LONG_MAX, - allocated_block_count - kMaxBlockCount + 1); + // allocation failed, try to reuse an unused block + block = _GetUnusedBlock(); + if (block == NULL) { + TB(Error(this, blockNumber, "get unused failed")); + FATAL(("could not allocate block!\n")); + return NULL; + } } block->current_data = Allocate(); - if (!block->current_data) { - FATAL(("could not allocate block data!\n")); - delete block; + if (block->current_data == NULL) { + object_cache_free(sBlockCache, block); return NULL; } @@ -270,59 +888,93 @@ block_cache::NewBlock(off_t blockNumber) block->parent_data = NULL; block->is_dirty = false; block->unused = false; -#ifdef DEBUG_CHANGED + block->discard = false; +#if BLOCK_CACHE_DEBUG_CHANGED block->compare = NULL; #endif - allocated_block_count++; - return block; } void -block_cache::RemoveUnusedBlocks(int32 maxAccessed, int32 count) +block_cache::RemoveBlock(cached_block* block) { - TRACE(("block_cache: remove up to %ld unused blocks\n", count)); + hash_remove(hash, block); + FreeBlock(block); +} - for (block_list::Iterator it = unused_blocks.GetIterator(); - cached_block *block = it.Next();) { - if (maxAccessed < block->accessed) - continue; +/*! Discards the block from a transaction (this method must not be called + for blocks not part of a transaction). +*/ +void +block_cache::DiscardBlock(cached_block* block) +{ + ASSERT(block->discard); - TRACE((" remove block %Ld, accessed %ld times\n", - block->block_number, block->accessed)); + if (block->parent_data != NULL && block->parent_data != block->current_data) + Free(block->parent_data); + block->parent_data = NULL; + + if (block->original_data != NULL) { + Free(block->original_data); + block->original_data = NULL; + } + + RemoveBlock(block); +} + + +cached_block* +block_cache::_GetUnusedBlock() +{ + TRACE(("block_cache: get unused block\n")); + + for (block_list::Iterator iterator = unused_blocks.GetIterator(); + cached_block* block = iterator.Next();) { + TB(Flush(this, block, true)); // this can only happen if no transactions are used if (block->is_dirty) write_cached_block(this, block, false); // remove block from lists - it.Remove(); + iterator.Remove(); hash_remove(hash, block); - FreeBlock(block); + // TODO: see if parent/compare data is handled correctly here! + if (block->parent_data != NULL + && block->parent_data != block->original_data) + Free(block->parent_data); + if (block->original_data != NULL) + Free(block->original_data); - if (--count <= 0) - break; + return block; } + + return NULL; } // #pragma mark - private block functions +/*! Removes a reference from the specified \a block. If this was the last + reference, the block is moved into the unused list. + In low memory situations, it will also free some blocks from that list, + but not necessarily the \a block it just released. +*/ static void -put_cached_block(block_cache *cache, cached_block *block) +put_cached_block(block_cache* cache, cached_block* block) { -#ifdef DEBUG_CHANGED +#if BLOCK_CACHE_DEBUG_CHANGED if (!block->is_dirty && block->compare != NULL && memcmp(block->current_data, block->compare, cache->block_size)) { dprintf("new block:\n"); - dump_block((const char *)block->current_data, 256, " "); + dump_block((const char*)block->current_data, 256, " "); dprintf("unchanged block:\n"); - dump_block((const char *)block->compare, 256, " "); + dump_block((const char*)block->compare, 256, " "); write_cached_block(cache, block); panic("block_cache: supposed to be clean block was changed!\n"); @@ -330,38 +982,47 @@ put_cached_block(block_cache *cache, cached_block *block) block->compare = NULL; } #endif + TB(Put(cache, block)); - if (--block->ref_count == 0 - && block->transaction == NULL - && block->previous_transaction == NULL) { - // put this block in the list of unused blocks - block->unused = true; - cache->unused_blocks.Add(block); + if (block->ref_count < 1) { + panic("Invalid ref_count for block %p, cache %p\n", block, cache); + return; } - if (cache->allocated_block_count > kMaxBlockCount) { - cache->RemoveUnusedBlocks(LONG_MAX, - cache->allocated_block_count - kMaxBlockCount); + if (--block->ref_count == 0 + && block->transaction == NULL && block->previous_transaction == NULL) { + // This block is not used anymore, and not part of any transaction + if (block->discard) { + cache->RemoveBlock(block); + } else { + // put this block in the list of unused blocks + block->unused = true; + ASSERT(block->original_data == NULL + && block->parent_data == NULL); + cache->unused_blocks.Add(block); + } } } static void -put_cached_block(block_cache *cache, off_t blockNumber) +put_cached_block(block_cache* cache, off_t blockNumber) { if (blockNumber < 0 || blockNumber >= cache->max_blocks) { panic("put_cached_block: invalid block number %lld (max %lld)", blockNumber, cache->max_blocks - 1); } - - cached_block *block = (cached_block *)hash_lookup(cache->hash, &blockNumber); + + cached_block* block = (cached_block*)hash_lookup(cache->hash, &blockNumber); if (block != NULL) put_cached_block(cache, block); + else { + TB(Error(cache, blockNumber, "put unknown")); + } } -/*! - Retrieves the block \a blockNumber from the hash table, if it's already +/*! Retrieves the block \a blockNumber from the hash table, if it's already there, or reads it from the disk. \param _allocated tells you wether or not a new block has been allocated @@ -370,8 +1031,8 @@ put_cached_block(block_cache *cache, off_t blockNumber) not already in the cache. The block you retrieve may contain random data. */ -static cached_block * -get_cached_block(block_cache *cache, off_t blockNumber, bool *_allocated, +static cached_block* +get_cached_block(block_cache* cache, off_t blockNumber, bool* _allocated, bool readBlock = true) { if (blockNumber < 0 || blockNumber >= cache->max_blocks) { @@ -380,7 +1041,7 @@ get_cached_block(block_cache *cache, off_t blockNumber, bool *_allocated, return NULL; } - cached_block *block = (cached_block *)hash_lookup(cache->hash, + cached_block* block = (cached_block*)hash_lookup(cache->hash, &blockNumber); *_allocated = false; @@ -390,37 +1051,24 @@ get_cached_block(block_cache *cache, off_t blockNumber, bool *_allocated, if (block == NULL) return NULL; - hash_insert(cache->hash, block); + hash_insert_grow(cache->hash, block); *_allocated = true; - } else { - // TODO: currently, the data is always mapped in -/* - if (block->ref_count == 0 && block->current_data != NULL) { - // see if the old block can be resurrected - block->current_data = cache->allocator->Acquire(block->current_data); - } - - if (block->current_data == NULL) { - // there is no block yet, but we need one - block->current_data = cache->allocator->Get(); - if (block->current_data == NULL) - return NULL; - - *_allocated = true; - } -*/ } if (*_allocated && readBlock) { int32 blockSize = cache->block_size; - if (read_pos(cache->fd, blockNumber * blockSize, block->current_data, - blockSize) < blockSize) { - hash_remove(cache->hash, block); - cache->FreeBlock(block); - FATAL(("could not read block %Ld\n", blockNumber)); + ssize_t bytesRead = read_pos(cache->fd, blockNumber * blockSize, + block->current_data, blockSize); + if (bytesRead < blockSize) { + cache->RemoveBlock(block); + TB(Error(cache, blockNumber, "read failed", bytesRead)); + + FATAL(("could not read block %Ld: bytesRead: %ld, error: %s\n", + blockNumber, bytesRead, strerror(errno))); return NULL; } + TB(Read(cache, block)); } if (block->unused) { @@ -436,16 +1084,15 @@ get_cached_block(block_cache *cache, off_t blockNumber, bool *_allocated, } -/*! - Returns the writable block data for the requested blockNumber. +/*! Returns the writable block data for the requested blockNumber. If \a cleared is true, the block is not read from disk; an empty block is returned. This is the only method to insert a block into a transaction. It makes sure that the previous block contents are preserved in that case. */ -static void * -get_writable_cached_block(block_cache *cache, off_t blockNumber, off_t base, +static void* +get_writable_cached_block(block_cache* cache, off_t blockNumber, off_t base, off_t length, int32 transactionID, bool cleared) { TRACE(("get_writable_cached_block(blockNumber = %Ld, transaction = %ld)\n", @@ -457,32 +1104,40 @@ get_writable_cached_block(block_cache *cache, off_t blockNumber, off_t base, } bool allocated; - cached_block *block = get_cached_block(cache, blockNumber, &allocated, + cached_block* block = get_cached_block(cache, blockNumber, &allocated, !cleared); if (block == NULL) return NULL; + block->discard = false; + // if there is no transaction support, we just return the current block if (transactionID == -1) { if (cleared) memset(block->current_data, 0, cache->block_size); - block->is_dirty = true; - // mark the block as dirty + if (!block->is_dirty) { + cache->num_dirty_blocks++; + block->is_dirty = true; + // mark the block as dirty + } + TB(Get(cache, block)); return block->current_data; } - if (block->transaction != NULL && block->transaction->id != transactionID) { - // ToDo: we have to wait here until the other transaction is done. + cache_transaction* transaction = block->transaction; + + if (transaction != NULL && transaction->id != transactionID) { + // TODO: we have to wait here until the other transaction is done. // Maybe we should even panic, since we can't prevent any deadlocks. panic("get_writable_cached_block(): asked to get busy writable block (transaction %ld)\n", block->transaction->id); put_cached_block(cache, block); return NULL; } - if (block->transaction == NULL && transactionID != -1) { + if (transaction == NULL && transactionID != -1) { // get new transaction - cache_transaction *transaction = lookup_transaction(cache, transactionID); + transaction = lookup_transaction(cache, transactionID); if (transaction == NULL) { panic("get_writable_cached_block(): invalid transaction %ld!\n", transactionID); @@ -502,11 +1157,17 @@ get_writable_cached_block(block_cache *cache, off_t blockNumber, off_t base, transaction->first_block = block; transaction->num_blocks++; } + if (transaction != NULL) + transaction->last_used = system_time(); + + bool wasUnchanged = block->original_data == NULL + || block->previous_transaction != NULL; if (!(allocated && cleared) && block->original_data == NULL) { // we already have data, so we need to preserve it block->original_data = cache->Allocate(); if (block->original_data == NULL) { + TB(Error(cache, blockNumber, "allocate original failed")); FATAL(("could not allocate original_data\n")); put_cached_block(cache, block); return NULL; @@ -519,46 +1180,61 @@ get_writable_cached_block(block_cache *cache, off_t blockNumber, off_t base, block->parent_data = cache->Allocate(); if (block->parent_data == NULL) { // TODO: maybe we should just continue the current transaction in this case... + TB(Error(cache, blockNumber, "allocate parent failed")); FATAL(("could not allocate parent\n")); put_cached_block(cache, block); return NULL; } memcpy(block->parent_data, block->current_data, cache->block_size); - block->transaction->sub_num_blocks++; - } + transaction->sub_num_blocks++; + } else if (transaction != NULL && transaction->has_sub_transaction + && block->parent_data == NULL && wasUnchanged) + transaction->sub_num_blocks++; if (cleared) memset(block->current_data, 0, cache->block_size); block->is_dirty = true; + TB(Get(cache, block)); return block->current_data; } +/*! Writes the specified \a block back to disk. It will always only write back + the oldest change of the block if it is part of more than one transaction. + It will automatically send out TRANSACTION_WRITTEN notices, as well as + delete transactions when they are no longer used, and \a deleteTransaction + is \c true. +*/ static status_t -write_cached_block(block_cache *cache, cached_block *block, +write_cached_block(block_cache* cache, cached_block* block, bool deleteTransaction) { - cache_transaction *previous = block->previous_transaction; + cache_transaction* previous = block->previous_transaction; int32 blockSize = cache->block_size; - void *data = previous && block->original_data + void* data = previous && block->original_data ? block->original_data : block->current_data; // we first need to write back changes from previous transactions TRACE(("write_cached_block(block %Ld)\n", block->block_number)); + TB(Write(cache, block)); ssize_t written = write_pos(cache->fd, block->block_number * blockSize, data, blockSize); if (written < blockSize) { + TB(Error(cache, block->block_number, "write failed", written)); FATAL(("could not write back block %Ld (%s)\n", block->block_number, strerror(errno))); return B_IO_ERROR; } + if (cache->num_dirty_blocks > 0) + cache->num_dirty_blocks--; + if (data == block->current_data) block->is_dirty = false; @@ -566,14 +1242,19 @@ write_cached_block(block_cache *cache, cached_block *block, previous->blocks.Remove(block); block->previous_transaction = NULL; + if (block->original_data != NULL && block->transaction == NULL) { + // This block is not part of a transaction, so it does not need + // its original pointer anymore. + cache->Free(block->original_data); + block->original_data = NULL; + } + // Has the previous transation been finished with that write? if (--previous->num_blocks == 0) { TRACE(("cache transaction %ld finished!\n", previous->id)); + T(Action("written", cache, previous)); - if (previous->notification_hook != NULL) { - previous->notification_hook(previous->id, - previous->notification_data); - } + notify_transaction_listeners(cache, previous, TRANSACTION_WRITTEN); if (deleteTransaction) { hash_remove(cache->transaction_hash, previous); @@ -581,33 +1262,532 @@ write_cached_block(block_cache *cache, cached_block *block, } } } + if (block->transaction == NULL && block->ref_count == 0) { + // the block is no longer used + block->unused = true; + cache->unused_blocks.Add(block); + } return B_OK; } -status_t -block_cache_init() +#if DEBUG_BLOCK_CACHE + +static void +dump_block(cached_block* block) { + kprintf("%08lx %9Ld %08lx %08lx %08lx %5ld %6ld %c%c%c%c%c %08lx %08lx\n", + (addr_t)block, block->block_number, + (addr_t)block->current_data, (addr_t)block->original_data, + (addr_t)block->parent_data, block->ref_count, block->accessed, + block->busy ? 'B' : '-', block->is_writing ? 'W' : '-', + block->is_dirty ? 'D' : '-', block->unused ? 'U' : '-', + block->discard ? 'D' : '-', + (addr_t)block->transaction, + (addr_t)block->previous_transaction); +} + + +static int +dump_cache(int argc, char** argv) +{ + bool showTransactions = false; + bool showBlocks = false; + int32 i = 1; + while (argv[i] != NULL && argv[i][0] == '-') { + for (char* arg = &argv[i][1]; arg[0]; arg++) { + switch (arg[0]) { + case 'b': + showBlocks = true; + break; + case 't': + showTransactions = true; + break; + default: + print_debugger_command_usage(argv[0]); + return 0; + } + } + i++; + } + + if (i >= argc) { + print_debugger_command_usage(argv[0]); + return 0; + } + + block_cache* cache = (struct block_cache*)parse_expression(argv[i]); + if (cache == NULL) { + kprintf("invalid cache address\n"); + return 0; + } + + off_t blockNumber = -1; + if (i + 1 < argc) { + blockNumber = parse_expression(argv[i + 1]); + cached_block* block = (cached_block*)hash_lookup(cache->hash, + &blockNumber); + if (block != NULL) { + kprintf("BLOCK %p\n", block); + kprintf(" current data: %p\n", block->current_data); + kprintf(" original data: %p\n", block->original_data); + kprintf(" parent data: %p\n", block->parent_data); + kprintf(" ref_count: %ld\n", block->ref_count); + kprintf(" accessed: %ld\n", block->accessed); + kprintf(" flags: "); + if (block->is_writing) + kprintf(" is-writing"); + if (block->is_dirty) + kprintf(" is-dirty"); + if (block->unused) + kprintf(" unused"); + if (block->discard) + kprintf(" discard"); + kprintf("\n"); + if (block->transaction != NULL) { + kprintf(" transaction: %p (%ld)\n", block->transaction, + block->transaction->id); + if (block->transaction_next != NULL) { + kprintf(" next in transaction: %Ld\n", + block->transaction_next->block_number); + } + } + if (block->previous_transaction != NULL) { + kprintf(" previous transaction: %p (%ld)\n", + block->previous_transaction, + block->previous_transaction->id); + } + + set_debug_variable("_current", (addr_t)block->current_data); + set_debug_variable("_original", (addr_t)block->original_data); + set_debug_variable("_parent", (addr_t)block->parent_data); + } else + kprintf("block %Ld not found\n", blockNumber); + return 0; + } + + kprintf("BLOCK CACHE: %p\n", cache); + + kprintf(" fd: %d\n", cache->fd); + kprintf(" max_blocks: %Ld\n", cache->max_blocks); + kprintf(" block_size: %lu\n", cache->block_size); + kprintf(" next_transaction_id: %ld\n", cache->next_transaction_id); + + if (!cache->pending_notifications.IsEmpty()) { + kprintf(" pending notifications:\n"); + + NotificationList::Iterator iterator + = cache->pending_notifications.GetIterator(); + while (iterator.HasNext()) { + cache_notification* notification = iterator.Next(); + + kprintf(" %p %5lx %p - %p\n", notification, + notification->events_pending, notification->hook, + notification->data); + } + } + + if (showTransactions) { + kprintf(" transactions:\n"); + kprintf("address id state blocks main sub\n"); + + hash_iterator iterator; + hash_open(cache->transaction_hash, &iterator); + + cache_transaction* transaction; + while ((transaction = (cache_transaction*)hash_next( + cache->transaction_hash, &iterator)) != NULL) { + kprintf("%p %5ld %-7s %5ld %5ld %5ld\n", transaction, + transaction->id, transaction->open ? "open" : "closed", + transaction->num_blocks, transaction->main_num_blocks, + transaction->sub_num_blocks); + } + } + + if (showBlocks) { + kprintf(" blocks:\n"); + kprintf("address block no. current original parent refs access " + "flags transact prev. trans\n"); + } + + uint32 referenced = 0; + uint32 count = 0; + uint32 dirty = 0; + uint32 discarded = 0; + hash_iterator iterator; + hash_open(cache->hash, &iterator); + cached_block* block; + while ((block = (cached_block*)hash_next(cache->hash, &iterator)) != NULL) { + if (showBlocks) + dump_block(block); + + if (block->is_dirty) + dirty++; + if (block->discard) + discarded++; + if (block->ref_count) + referenced++; + count++; + } + + kprintf(" %ld blocks total, %ld dirty, %ld discarded, %ld referenced, %ld " + "in unused.\n", count, dirty, discarded, referenced, + cache->unused_blocks.Size()); + + hash_close(cache->hash, &iterator, false); + return 0; +} + + +static int +dump_transaction(int argc, char** argv) +{ + bool showBlocks = false; + int i = 1; + if (argc > 1 && !strcmp(argv[1], "-b")) { + showBlocks = true; + i++; + } + + if (argc - i < 1 || argc - i > 2) { + print_debugger_command_usage(argv[0]); + return 0; + } + + cache_transaction* transaction = NULL; + + if (argc - i == 1) { + transaction = (cache_transaction*)parse_expression(argv[i]); + } else { + block_cache* cache = (block_cache*)parse_expression(argv[i]); + int32 id = parse_expression(argv[i + 1]); + transaction = lookup_transaction(cache, id); + if (transaction == NULL) { + kprintf("No transaction with ID %ld found.\n", id); + return 0; + } + } + + kprintf("TRANSACTION %p\n", transaction); + + kprintf(" id: %ld\n", transaction->id); + kprintf(" num block: %ld\n", transaction->num_blocks); + kprintf(" main num block: %ld\n", transaction->main_num_blocks); + kprintf(" sub num block: %ld\n", transaction->sub_num_blocks); + kprintf(" has sub: %d\n", transaction->has_sub_transaction); + kprintf(" state: %s\n", transaction->open ? "open" : "closed"); + kprintf(" idle: %Ld secs\n", + (system_time() - transaction->last_used) / 1000000); + + kprintf(" listeners:\n"); + + ListenerList::Iterator iterator = transaction->listeners.GetIterator(); + while (iterator.HasNext()) { + cache_listener* listener = iterator.Next(); + + kprintf(" %p %5lx %p - %p\n", listener, listener->events_pending, + listener->hook, listener->data); + } + + if (!showBlocks) + return 0; + + kprintf(" blocks:\n"); + kprintf("address block no. current original parent refs access " + "flags transact prev. trans\n"); + + cached_block* block = transaction->first_block; + while (block != NULL) { + dump_block(block); + block = block->transaction_next; + } + + kprintf("--\n"); + + block_list::Iterator blockIterator = transaction->blocks.GetIterator(); + while (blockIterator.HasNext()) { + block = blockIterator.Next(); + dump_block(block); + } + + return 0; +} + + +static int +dump_caches(int argc, char** argv) +{ + kprintf("Block caches:\n"); + DoublyLinkedList::Iterator i = sCaches.GetIterator(); + while (i.HasNext()) { + block_cache* cache = i.Next(); + if (cache == (block_cache*)&sMarkCache) + continue; + + kprintf(" %p\n", cache); + } + + return 0; +} + +#endif // DEBUG_BLOCK_CACHE + + +/*! Traverses through the block_cache list, and returns one cache after the + other. The cache returned is automatically locked when you get it, and + unlocked with the next call to this function. Ignores caches that are in + deletion state. + Returns \c NULL when the end of the list is reached. +*/ +static block_cache* +get_next_locked_block_cache(block_cache* last) +{ + MutexLocker _(sCachesLock); + + block_cache* cache; + if (last != NULL) { + mutex_unlock(&last->lock); + + cache = sCaches.GetNext((block_cache*)&sMarkCache); + sCaches.Remove((block_cache*)&sMarkCache); + } else + cache = sCaches.Head(); + + if (cache != NULL) { + mutex_lock(&cache->lock); + sCaches.Insert(sCaches.GetNext(cache), (block_cache*)&sMarkCache); + } + + return cache; +} + + +/*! Background thread that continuously checks for pending notifications of + all caches. + Every two seconds, it will also write back up to 64 blocks per cache. +*/ +static status_t +block_notifier_and_writer(void* /*data*/) +{ + const bigtime_t kTimeout = 2000000LL; + bigtime_t timeout = kTimeout; + + while (true) { + bigtime_t start = system_time(); + + status_t status = acquire_sem_etc(sEventSemaphore, 1, + B_RELATIVE_TIMEOUT, timeout); + if (status == B_OK) { + flush_pending_notifications(); + timeout -= system_time() - start; + continue; + } + + // write 64 blocks of each block_cache every two seconds + // TODO: change this once we have an I/O scheduler + timeout = kTimeout; + + block_cache* cache = NULL; + while ((cache = get_next_locked_block_cache(cache)) != NULL) { + const uint32 kMaxCount = 64; + cached_block* blocks[kMaxCount]; + uint32 count = 0; + + if (cache->num_dirty_blocks) { + // This cache is not using transactions, we'll scan the blocks + // directly + hash_iterator iterator; + hash_open(cache->hash, &iterator); + + cached_block* block; + while (count < kMaxCount + && (block = (cached_block*)hash_next(cache->hash, + &iterator)) != NULL) { + if (block->is_dirty) + blocks[count++] = block; + } + + hash_close(cache->hash, &iterator, false); + } else { + hash_iterator iterator; + hash_open(cache->transaction_hash, &iterator); + + cache_transaction* transaction; + while ((transaction = (cache_transaction*)hash_next( + cache->transaction_hash, &iterator)) != NULL + && count < kMaxCount) { + if (transaction->open) { + if (system_time() > transaction->last_used + + kTransactionIdleTime) { + // Transaction is open but idle + notify_transaction_listeners(cache, transaction, + TRANSACTION_IDLE); + } + continue; + } + + // sort blocks to speed up writing them back + // TODO: ideally, this should be handled by the I/O scheduler + block_list::Iterator iterator + = transaction->blocks.GetIterator(); + + for (; count < kMaxCount && iterator.HasNext(); count++) { + blocks[count] = iterator.Next(); + } + } + + hash_close(cache->transaction_hash, &iterator, false); + } + + qsort(blocks, count, sizeof(void*), &compare_blocks); + + for (uint32 i = 0; i < count; i++) { + if (write_cached_block(cache, blocks[i], true) != B_OK) + break; + } + } + } +} + + +/*! Notify function for wait_for_notifications(). */ +static void +notify_sync(int32 transactionID, int32 event, void* _cache) +{ + block_cache* cache = (block_cache*)_cache; + + cache->condition_variable.NotifyOne(); +} + + +/*! Must be called with the sCachesLock held. */ +static bool +is_valid_cache(block_cache* cache) +{ + ASSERT_LOCKED_MUTEX(&sCachesLock); + + DoublyLinkedList::Iterator iterator = sCaches.GetIterator(); + while (iterator.HasNext()) { + if (cache == iterator.Next()) + return true; + } + + return false; +} + + +/*! Waits until all pending notifications are carried out. + Safe to be called from the block writer/notifier thread. + You must not hold the \a cache lock when calling this function. +*/ +static void +wait_for_notifications(block_cache* cache) +{ + MutexLocker locker(sCachesLock); + + if (find_thread(NULL) == sNotifierWriterThread) { + // We're the notifier thread, don't wait, but flush all pending + // notifications directly. + if (is_valid_cache(cache)) + flush_pending_notifications(cache); + return; + } + + // add sync notification + cache_notification notification; + set_notification(NULL, notification, TRANSACTION_WRITTEN, notify_sync, + cache); + + ConditionVariableEntry entry; + cache->condition_variable.Add(&entry); + + add_notification(cache, ¬ification, TRANSACTION_WRITTEN, false); + locker.Unlock(); + + // wait for notification hook to be called + entry.Wait(); + + ASSERT(notification.GetDoublyLinkedListLink()->next == NULL + && notification.GetDoublyLinkedListLink()->previous == NULL + && cache->pending_notifications.Head() != ¬ification); +} + + +status_t +block_cache_init(void) +{ + sBlockCache = create_object_cache_etc("cached blocks", sizeof(cached_block), + 8, 0, CACHE_LARGE_SLAB, NULL, NULL, NULL, NULL); + if (sBlockCache == NULL) + return B_NO_MEMORY; + + new (&sCaches) DoublyLinkedList; + // manually call constructor + + sEventSemaphore = create_sem(0, "block cache event"); + if (sEventSemaphore < B_OK) + return sEventSemaphore; + + sNotifierWriterThread = spawn_kernel_thread(&block_notifier_and_writer, + "block notifier/writer", B_LOW_PRIORITY, NULL); + if (sNotifierWriterThread >= B_OK) + resume_thread(sNotifierWriterThread); + +#if DEBUG_BLOCK_CACHE + add_debugger_command_etc("block_caches", &dump_caches, + "dumps all block caches", "\n", 0); + add_debugger_command_etc("block_cache", &dump_cache, + "dumps a specific block cache", + "[-bt] [block-number]\n" + " -t lists the transactions\n" + " -b lists all blocks\n", 0); + add_debugger_command_etc("transaction", &dump_transaction, + "dumps a specific transaction", "[-b] (( ) | )\n" + "Either use a block cache pointer and an ID or a pointer to the transaction.\n" + " -b lists all blocks that are part of this transaction\n", 0); +#endif + return B_OK; } +size_t +block_cache_used_memory() +{ + size_t usedMemory = 0; + + MutexLocker _(sCachesLock); + + DoublyLinkedList::Iterator it = sCaches.GetIterator(); + while (block_cache* cache = it.Next()) { + if (cache == (block_cache*)&sMarkCache) + continue; + + size_t cacheUsedMemory; + object_cache_get_usage(cache->buffer_cache, &cacheUsedMemory); + usedMemory += cacheUsedMemory; + } + + return usedMemory; +} + + // #pragma mark - public transaction API int32 -cache_start_transaction(void *_cache) +cache_start_transaction(void* _cache) { - block_cache *cache = (block_cache *)_cache; - BenaphoreLocker locker(&cache->lock); + block_cache* cache = (block_cache*)_cache; + MutexLocker locker(&cache->lock); if (cache->last_transaction && cache->last_transaction->open) { panic("last transaction (%ld) still open!\n", cache->last_transaction->id); } - cache_transaction *transaction = new(nothrow) cache_transaction; + cache_transaction* transaction = new(nothrow) cache_transaction; if (transaction == NULL) return B_NO_MEMORY; @@ -615,33 +1795,63 @@ cache_start_transaction(void *_cache) cache->last_transaction = transaction; TRACE(("cache_start_transaction(): id %ld started\n", transaction->id)); + T(Action("start", cache, transaction)); - hash_insert(cache->transaction_hash, transaction); + hash_insert_grow(cache->transaction_hash, transaction); return transaction->id; } status_t -cache_sync_transaction(void *_cache, int32 id) +cache_sync_transaction(void* _cache, int32 id) { - block_cache *cache = (block_cache *)_cache; - BenaphoreLocker locker(&cache->lock); + block_cache* cache = (block_cache*)_cache; + MutexLocker locker(&cache->lock); status_t status = B_ENTRY_NOT_FOUND; + TRACE(("cache_sync_transaction(id %ld)\n", id)); + hash_iterator iterator; hash_open(cache->transaction_hash, &iterator); - cache_transaction *transaction; - while ((transaction = (cache_transaction *)hash_next( + cache_transaction* transaction; + while ((transaction = (cache_transaction*)hash_next( cache->transaction_hash, &iterator)) != NULL) { // close all earlier transactions which haven't been closed yet if (transaction->id <= id && !transaction->open) { // write back all of their remaining dirty blocks + T(Action("sync", cache, transaction)); while (transaction->num_blocks > 0) { - status = write_cached_block(cache, transaction->blocks.Head(), - false); + // sort blocks to speed up writing them back + // TODO: this should be handled by the I/O scheduler + block_list::Iterator iterator + = transaction->blocks.GetIterator(); + uint32 maxCount = transaction->num_blocks; + cached_block* buffer[16]; + cached_block** blocks = (cached_block**)malloc(maxCount + * sizeof(void*)); + if (blocks == NULL) { + maxCount = 16; + blocks = buffer; + } + + uint32 count = 0; + for (; count < maxCount && iterator.HasNext(); count++) { + blocks[count] = iterator.Next(); + } + qsort(blocks, count, sizeof(void*), &compare_blocks); + + for (uint32 i = 0; i < count; i++) { + status = write_cached_block(cache, blocks[i], false); + if (status != B_OK) + break; + } + + if (blocks != buffer) + free(blocks); + if (status != B_OK) return status; } @@ -652,31 +1862,43 @@ cache_sync_transaction(void *_cache, int32 id) } hash_close(cache->transaction_hash, &iterator, false); + locker.Unlock(); + + wait_for_notifications(cache); + // make sure that all pending TRANSACTION_WRITTEN notifications + // are handled after we return return B_OK; } status_t -cache_end_transaction(void *_cache, int32 id, - transaction_notification_hook hook, void *data) +cache_end_transaction(void* _cache, int32 id, + transaction_notification_hook hook, void* data) { - block_cache *cache = (block_cache *)_cache; - BenaphoreLocker locker(&cache->lock); + block_cache* cache = (block_cache*)_cache; + MutexLocker locker(&cache->lock); TRACE(("cache_end_transaction(id = %ld)\n", id)); - cache_transaction *transaction = lookup_transaction(cache, id); + cache_transaction* transaction = lookup_transaction(cache, id); if (transaction == NULL) { panic("cache_end_transaction(): invalid transaction ID\n"); return B_BAD_VALUE; } - transaction->notification_hook = hook; - transaction->notification_data = data; + notify_transaction_listeners(cache, transaction, TRANSACTION_ENDED); + + if (add_transaction_listener(cache, transaction, TRANSACTION_WRITTEN, hook, + data) != B_OK) { + return B_NO_MEMORY; + } + + T(Action("end", cache, transaction)); // iterate through all blocks and free the unchanged original contents - cached_block *block = transaction->first_block, *next; + cached_block* block = transaction->first_block; + cached_block* next; for (; block != NULL; block = next) { next = block->transaction_next; @@ -684,6 +1906,12 @@ cache_end_transaction(void *_cache, int32 id, // need to write back pending changes write_cached_block(cache, block); } + if (block->discard) { + // This block has been discarded in the transaction + cache->DiscardBlock(block); + transaction->num_blocks--; + continue; + } if (block->original_data != NULL) { cache->Free(block->original_data); @@ -704,28 +1932,31 @@ cache_end_transaction(void *_cache, int32 id, } transaction->open = false; - return B_OK; } status_t -cache_abort_transaction(void *_cache, int32 id) +cache_abort_transaction(void* _cache, int32 id) { - block_cache *cache = (block_cache *)_cache; - BenaphoreLocker locker(&cache->lock); + block_cache* cache = (block_cache*)_cache; + MutexLocker locker(&cache->lock); TRACE(("cache_abort_transaction(id = %ld)\n", id)); - cache_transaction *transaction = lookup_transaction(cache, id); + cache_transaction* transaction = lookup_transaction(cache, id); if (transaction == NULL) { panic("cache_abort_transaction(): invalid transaction ID\n"); return B_BAD_VALUE; } + T(Abort(cache, transaction)); + notify_transaction_listeners(cache, transaction, TRANSACTION_ABORTED); + // iterate through all blocks and restore their original contents - cached_block *block = transaction->first_block, *next; + cached_block* block = transaction->first_block; + cached_block* next; for (; block != NULL; block = next) { next = block->transaction_next; @@ -744,6 +1975,7 @@ cache_abort_transaction(void *_cache, int32 id) block->transaction_next = NULL; block->transaction = NULL; + block->discard = false; } hash_remove(cache->transaction_hash, transaction); @@ -752,21 +1984,20 @@ cache_abort_transaction(void *_cache, int32 id) } -/*! - Acknowledges the current parent transaction, and starts a new transaction +/*! Acknowledges the current parent transaction, and starts a new transaction from its sub transaction. The new transaction also gets a new transaction ID. */ int32 -cache_detach_sub_transaction(void *_cache, int32 id, - transaction_notification_hook hook, void *data) +cache_detach_sub_transaction(void* _cache, int32 id, + transaction_notification_hook hook, void* data) { - block_cache *cache = (block_cache *)_cache; - BenaphoreLocker locker(&cache->lock); + block_cache* cache = (block_cache*)_cache; + MutexLocker locker(&cache->lock); TRACE(("cache_detach_sub_transaction(id = %ld)\n", id)); - cache_transaction *transaction = lookup_transaction(cache, id); + cache_transaction* transaction = lookup_transaction(cache, id); if (transaction == NULL) { panic("cache_detach_sub_transaction(): invalid transaction ID\n"); return B_BAD_VALUE; @@ -775,18 +2006,26 @@ cache_detach_sub_transaction(void *_cache, int32 id, return B_BAD_VALUE; // create a new transaction for the sub transaction - cache_transaction *newTransaction = new(nothrow) cache_transaction; + cache_transaction* newTransaction = new(nothrow) cache_transaction; if (transaction == NULL) return B_NO_MEMORY; newTransaction->id = atomic_add(&cache->next_transaction_id, 1); + T(Detach(cache, transaction, newTransaction)); - transaction->notification_hook = hook; - transaction->notification_data = data; + notify_transaction_listeners(cache, transaction, TRANSACTION_ENDED); + + if (add_transaction_listener(cache, transaction, TRANSACTION_WRITTEN, hook, + data) != B_OK) { + delete newTransaction; + return B_NO_MEMORY; + } // iterate through all blocks and free the unchanged original contents - cached_block *block = transaction->first_block, *next, *last = NULL; + cached_block* block = transaction->first_block; + cached_block* last = NULL; + cached_block* next; for (; block != NULL; block = next) { next = block->transaction_next; @@ -794,16 +2033,20 @@ cache_detach_sub_transaction(void *_cache, int32 id, // need to write back pending changes write_cached_block(cache, block); } + if (block->discard) { + cache->DiscardBlock(block); + transaction->main_num_blocks--; + continue; + } - if (block->original_data != NULL && block->parent_data != NULL - && block->parent_data != block->current_data) { + if (block->original_data != NULL && block->parent_data != NULL) { // free the original data if the parent data of the transaction // will be made current - but keep them otherwise cache->Free(block->original_data); block->original_data = NULL; } - if (block->parent_data != NULL - && block->parent_data != block->current_data) { + if (block->parent_data == NULL + || block->parent_data != block->current_data) { // we need to move this block over to the new transaction block->original_data = block->parent_data; if (last == NULL) @@ -811,36 +2054,44 @@ cache_detach_sub_transaction(void *_cache, int32 id, else last->transaction_next = block; + block->transaction = newTransaction; last = block; + } else + block->transaction = NULL; + + if (block->parent_data != NULL) { + // move the block to the previous transaction list + transaction->blocks.Add(block); + block->previous_transaction = transaction; + block->parent_data = NULL; } - block->parent_data = NULL; - // move the block to the previous transaction list - transaction->blocks.Add(block); - - block->previous_transaction = transaction; block->transaction_next = NULL; - block->transaction = newTransaction; } - transaction->open = false; + newTransaction->num_blocks = transaction->sub_num_blocks; - hash_insert(cache->transaction_hash, newTransaction); + transaction->open = false; + transaction->has_sub_transaction = false; + transaction->num_blocks = transaction->main_num_blocks; + transaction->sub_num_blocks = 0; + + hash_insert_grow(cache->transaction_hash, newTransaction); cache->last_transaction = newTransaction; - return B_OK; + return newTransaction->id; } status_t -cache_abort_sub_transaction(void *_cache, int32 id) +cache_abort_sub_transaction(void* _cache, int32 id) { - block_cache *cache = (block_cache *)_cache; - BenaphoreLocker locker(&cache->lock); + block_cache* cache = (block_cache*)_cache; + MutexLocker locker(&cache->lock); TRACE(("cache_abort_sub_transaction(id = %ld)\n", id)); - cache_transaction *transaction = lookup_transaction(cache, id); + cache_transaction* transaction = lookup_transaction(cache, id); if (transaction == NULL) { panic("cache_abort_sub_transaction(): invalid transaction ID\n"); return B_BAD_VALUE; @@ -848,9 +2099,13 @@ cache_abort_sub_transaction(void *_cache, int32 id) if (!transaction->has_sub_transaction) return B_BAD_VALUE; + T(Abort(cache, transaction)); + notify_transaction_listeners(cache, transaction, TRANSACTION_ABORTED); + // revert all changes back to the version of the parent - cached_block *block = transaction->first_block, *next; + cached_block* block = transaction->first_block; + cached_block* next; for (; block != NULL; block = next) { next = block->transaction_next; @@ -870,34 +2125,52 @@ cache_abort_sub_transaction(void *_cache, int32 id) } block->parent_data = NULL; + block->discard = false; } // all subsequent changes will go into the main transaction transaction->has_sub_transaction = false; + transaction->sub_num_blocks = 0; + return B_OK; } status_t -cache_start_sub_transaction(void *_cache, int32 id) +cache_start_sub_transaction(void* _cache, int32 id) { - block_cache *cache = (block_cache *)_cache; - BenaphoreLocker locker(&cache->lock); + block_cache* cache = (block_cache*)_cache; + MutexLocker locker(&cache->lock); TRACE(("cache_start_sub_transaction(id = %ld)\n", id)); - cache_transaction *transaction = lookup_transaction(cache, id); + cache_transaction* transaction = lookup_transaction(cache, id); if (transaction == NULL) { panic("cache_start_sub_transaction(): invalid transaction ID %ld\n", id); return B_BAD_VALUE; } + notify_transaction_listeners(cache, transaction, TRANSACTION_ENDED); + // move all changed blocks up to the parent - cached_block *block = transaction->first_block, *next; + cached_block* block = transaction->first_block; + cached_block* last = NULL; + cached_block* next; for (; block != NULL; block = next) { next = block->transaction_next; + if (block->discard) { + // This block has been discarded in the parent transaction + if (last != NULL) + last->transaction_next = next; + else + transaction->first_block = next; + + cache->DiscardBlock(block); + transaction->num_blocks--; + continue; + } if (transaction->has_sub_transaction && block->parent_data != NULL && block->parent_data != block->current_data) { @@ -909,56 +2182,123 @@ cache_start_sub_transaction(void *_cache, int32 id) // we "allocate" the parent data lazily, that means, we don't copy // the data (and allocate memory for it) until we need to block->parent_data = block->current_data; + last = block; } // all subsequent changes will go into the sub transaction transaction->has_sub_transaction = true; + transaction->main_num_blocks = transaction->num_blocks; transaction->sub_num_blocks = 0; + T(Action("start-sub", cache, transaction)); return B_OK; } +/*! Adds a transaction listener that gets notified when the transaction + is ended, aborted, written, or idle as specified by \a events. + The listener gets automatically removed when the transaction ends. +*/ status_t -cache_next_block_in_transaction(void *_cache, int32 id, uint32 *_cookie, - off_t *_blockNumber, void **_data, void **_unchangedData) +cache_add_transaction_listener(void* _cache, int32 id, int32 events, + transaction_notification_hook hook, void* data) { - cached_block *block = (cached_block *)*_cookie; - block_cache *cache = (block_cache *)_cache; + block_cache* cache = (block_cache*)_cache; - BenaphoreLocker locker(&cache->lock); + MutexLocker locker(&cache->lock); - cache_transaction *transaction = lookup_transaction(cache, id); + cache_transaction* transaction = lookup_transaction(cache, id); if (transaction == NULL) return B_BAD_VALUE; + return add_transaction_listener(cache, transaction, events, hook, data); +} + + +status_t +cache_remove_transaction_listener(void* _cache, int32 id, + transaction_notification_hook hookFunction, void* data) +{ + block_cache* cache = (block_cache*)_cache; + + MutexLocker locker(&cache->lock); + + cache_transaction* transaction = lookup_transaction(cache, id); + if (transaction == NULL) + return B_BAD_VALUE; + + ListenerList::Iterator iterator = transaction->listeners.GetIterator(); + while (iterator.HasNext()) { + cache_listener* listener = iterator.Next(); + if (listener->data == data && listener->hook == hookFunction) { + iterator.Remove(); + + if (listener->events_pending != 0) { + MutexLocker _(sNotificationsLock); + if (listener->events_pending != 0) + cache->pending_notifications.Remove(listener); + } + delete listener; + return B_OK; + } + } + + return B_ENTRY_NOT_FOUND; +} + + +status_t +cache_next_block_in_transaction(void* _cache, int32 id, bool mainOnly, + long* _cookie, off_t* _blockNumber, void** _data, void** _unchangedData) +{ + cached_block* block = (cached_block*)*_cookie; + block_cache* cache = (block_cache*)_cache; + + MutexLocker locker(&cache->lock); + + cache_transaction* transaction = lookup_transaction(cache, id); + if (transaction == NULL || !transaction->open) + return B_BAD_VALUE; + if (block == NULL) block = transaction->first_block; else block = block->transaction_next; + if (transaction->has_sub_transaction) { + if (mainOnly) { + // find next block that the parent changed + while (block != NULL && block->parent_data == NULL) + block = block->transaction_next; + } else { + // find next non-discarded block + while (block != NULL && block->discard) + block = block->transaction_next; + } + } + if (block == NULL) return B_ENTRY_NOT_FOUND; if (_blockNumber) *_blockNumber = block->block_number; if (_data) - *_data = block->current_data; + *_data = mainOnly ? block->parent_data : block->current_data; if (_unchangedData) *_unchangedData = block->original_data; - *_cookie = (uint32)block; - return B_OK; + *_cookie = (addr_t)block; + return B_OK; } int32 -cache_blocks_in_transaction(void *_cache, int32 id) +cache_blocks_in_transaction(void* _cache, int32 id) { - block_cache *cache = (block_cache *)_cache; - BenaphoreLocker locker(&cache->lock); + block_cache* cache = (block_cache*)_cache; + MutexLocker locker(&cache->lock); - cache_transaction *transaction = lookup_transaction(cache, id); + cache_transaction* transaction = lookup_transaction(cache, id); if (transaction == NULL) return B_BAD_VALUE; @@ -967,12 +2307,26 @@ cache_blocks_in_transaction(void *_cache, int32 id) int32 -cache_blocks_in_sub_transaction(void *_cache, int32 id) +cache_blocks_in_main_transaction(void* _cache, int32 id) { - block_cache *cache = (block_cache *)_cache; - BenaphoreLocker locker(&cache->lock); + block_cache* cache = (block_cache*)_cache; + MutexLocker locker(&cache->lock); - cache_transaction *transaction = lookup_transaction(cache, id); + cache_transaction* transaction = lookup_transaction(cache, id); + if (transaction == NULL) + return B_BAD_VALUE; + + return transaction->main_num_blocks; +} + + +int32 +cache_blocks_in_sub_transaction(void* _cache, int32 id) +{ + block_cache* cache = (block_cache*)_cache; + MutexLocker locker(&cache->lock); + + cache_transaction* transaction = lookup_transaction(cache, id); if (transaction == NULL) return B_BAD_VALUE; @@ -981,33 +2335,36 @@ cache_blocks_in_sub_transaction(void *_cache, int32 id) // #pragma mark - public block cache API -// public interface void -block_cache_delete(void *_cache, bool allowWrites) +block_cache_delete(void* _cache, bool allowWrites) { - block_cache *cache = (block_cache *)_cache; + block_cache* cache = (block_cache*)_cache; if (allowWrites) - block_cache_sync(cache); + UserlandFS::HaikuKernelEmu::block_cache_sync(cache); - BenaphoreLocker locker(&cache->lock); + mutex_lock(&sCachesLock); + sCaches.Remove(cache); + mutex_unlock(&sCachesLock); + + mutex_lock(&cache->lock); // free all blocks uint32 cookie = 0; - cached_block *block; - while ((block = (cached_block *)hash_remove_first(cache->hash, + cached_block* block; + while ((block = (cached_block*)hash_remove_first(cache->hash, &cookie)) != NULL) { cache->FreeBlock(block); } - // free all transactions (they will all be aborted) + // free all transactions (they will all be aborted) cookie = 0; - cache_transaction *transaction; - while ((transaction = (cache_transaction *)hash_remove_first( + cache_transaction* transaction; + while ((transaction = (cache_transaction*)hash_remove_first( cache->transaction_hash, &cookie)) != NULL) { delete transaction; } @@ -1016,37 +2373,40 @@ block_cache_delete(void *_cache, bool allowWrites) } -void * +void* block_cache_create(int fd, off_t numBlocks, size_t blockSize, bool readOnly) { - block_cache *cache = new(nothrow) block_cache(fd, numBlocks, blockSize, + block_cache* cache = new(nothrow) block_cache(fd, numBlocks, blockSize, readOnly); if (cache == NULL) return NULL; - if (cache->InitCheck() != B_OK) { + if (cache->Init() != B_OK) { delete cache; return NULL; } + MutexLocker _(sCachesLock); + sCaches.Add(cache); + return cache; } status_t -block_cache_sync(void *_cache) +block_cache_sync(void* _cache) { - block_cache *cache = (block_cache *)_cache; + block_cache* cache = (block_cache*)_cache; // we will sync all dirty blocks to disk that have a completed // transaction or no transaction only - BenaphoreLocker locker(&cache->lock); + MutexLocker locker(&cache->lock); hash_iterator iterator; hash_open(cache->hash, &iterator); - cached_block *block; - while ((block = (cached_block *)hash_next(cache->hash, &iterator)) != NULL) { + cached_block* block; + while ((block = (cached_block*)hash_next(cache->hash, &iterator)) != NULL) { if (block->previous_transaction != NULL || (block->transaction == NULL && block->is_dirty)) { status_t status = write_cached_block(cache, block); @@ -1056,14 +2416,19 @@ block_cache_sync(void *_cache) } hash_close(cache->hash, &iterator, false); + locker.Unlock(); + + wait_for_notifications(cache); + // make sure that all pending TRANSACTION_WRITTEN notifications + // are handled after we return return B_OK; } status_t -block_cache_sync_etc(void *_cache, off_t blockNumber, size_t numBlocks) +block_cache_sync_etc(void* _cache, off_t blockNumber, size_t numBlocks) { - block_cache *cache = (block_cache *)_cache; + block_cache* cache = (block_cache*)_cache; // we will sync all dirty blocks to disk that have a completed // transaction or no transaction only @@ -1074,13 +2439,14 @@ block_cache_sync_etc(void *_cache, off_t blockNumber, size_t numBlocks) return B_BAD_VALUE; } - BenaphoreLocker locker(&cache->lock); + MutexLocker locker(&cache->lock); for (; numBlocks > 0; numBlocks--, blockNumber++) { - cached_block *block = (cached_block *)hash_lookup(cache->hash, + cached_block* block = (cached_block*)hash_lookup(cache->hash, &blockNumber); if (block == NULL) continue; + if (block->previous_transaction != NULL || (block->transaction == NULL && block->is_dirty)) { status_t status = write_cached_block(cache, block); @@ -1089,24 +2455,61 @@ block_cache_sync_etc(void *_cache, off_t blockNumber, size_t numBlocks) } } + locker.Unlock(); + + wait_for_notifications(cache); + // make sure that all pending TRANSACTION_WRITTEN notifications + // are handled after we return return B_OK; } -status_t -block_cache_make_writable(void *_cache, off_t blockNumber, int32 transaction) +void +block_cache_discard(void* _cache, off_t blockNumber, size_t numBlocks) { - block_cache *cache = (block_cache *)_cache; - BenaphoreLocker locker(&cache->lock); + block_cache* cache = (block_cache*)_cache; + MutexLocker locker(&cache->lock); + + for (; numBlocks > 0; numBlocks--, blockNumber++) { + cached_block* block = (cached_block*)hash_lookup(cache->hash, + &blockNumber); + if (block == NULL) + continue; + + if (block->previous_transaction != NULL) + write_cached_block(cache, block); + + if (block->unused) { + cache->unused_blocks.Remove(block); + cache->RemoveBlock(block); + } else { + if (block->transaction != NULL && block->parent_data != NULL + && block->parent_data != block->current_data) { + panic("Discarded block %Ld has already been changed in this " + "transaction!", blockNumber); + } + + // mark it as discarded (in the current transaction only, if any) + block->discard = true; + } + } +} + + +status_t +block_cache_make_writable(void* _cache, off_t blockNumber, int32 transaction) +{ + block_cache* cache = (block_cache*)_cache; + MutexLocker locker(&cache->lock); if (cache->read_only) panic("tried to make block writable on a read-only cache!"); - // ToDo: this can be done better! - void *block = get_writable_cached_block(cache, blockNumber, + // TODO: this can be done better! + void* block = get_writable_cached_block(cache, blockNumber, blockNumber, 1, transaction, false); if (block != NULL) { - put_cached_block((block_cache *)_cache, blockNumber); + put_cached_block((block_cache*)_cache, blockNumber); return B_OK; } @@ -1114,12 +2517,12 @@ block_cache_make_writable(void *_cache, off_t blockNumber, int32 transaction) } -void * -block_cache_get_writable_etc(void *_cache, off_t blockNumber, off_t base, +void* +block_cache_get_writable_etc(void* _cache, off_t blockNumber, off_t base, off_t length, int32 transaction) { - block_cache *cache = (block_cache *)_cache; - BenaphoreLocker locker(&cache->lock); + block_cache* cache = (block_cache*)_cache; + MutexLocker locker(&cache->lock); TRACE(("block_cache_get_writable_etc(block = %Ld, transaction = %ld)\n", blockNumber, transaction)); @@ -1131,60 +2534,61 @@ block_cache_get_writable_etc(void *_cache, off_t blockNumber, off_t base, } -void * -block_cache_get_writable(void *_cache, off_t blockNumber, int32 transaction) +void* +block_cache_get_writable(void* _cache, off_t blockNumber, int32 transaction) { return block_cache_get_writable_etc(_cache, blockNumber, blockNumber, 1, transaction); } -void * -block_cache_get_empty(void *_cache, off_t blockNumber, int32 transaction) +void* +block_cache_get_empty(void* _cache, off_t blockNumber, int32 transaction) { - block_cache *cache = (block_cache *)_cache; - BenaphoreLocker locker(&cache->lock); + block_cache* cache = (block_cache*)_cache; + MutexLocker locker(&cache->lock); TRACE(("block_cache_get_empty(block = %Ld, transaction = %ld)\n", blockNumber, transaction)); if (cache->read_only) panic("tried to get empty writable block on a read-only cache!"); - return get_writable_cached_block((block_cache *)_cache, blockNumber, - blockNumber, 1, transaction, true); + return get_writable_cached_block((block_cache*)_cache, blockNumber, + blockNumber, 1, transaction, true); } -const void * -block_cache_get_etc(void *_cache, off_t blockNumber, off_t base, off_t length) +const void* +block_cache_get_etc(void* _cache, off_t blockNumber, off_t base, off_t length) { - block_cache *cache = (block_cache *)_cache; - BenaphoreLocker locker(&cache->lock); + block_cache* cache = (block_cache*)_cache; + MutexLocker locker(&cache->lock); bool allocated; - cached_block *block = get_cached_block(cache, blockNumber, &allocated); + cached_block* block = get_cached_block(cache, blockNumber, &allocated); if (block == NULL) return NULL; -#ifdef DEBUG_CHANGED +#if BLOCK_CACHE_DEBUG_CHANGED if (block->compare == NULL) block->compare = cache->Allocate(); if (block->compare != NULL) memcpy(block->compare, block->current_data, cache->block_size); #endif + TB(Get(cache, block)); + return block->current_data; } -const void * -block_cache_get(void *_cache, off_t blockNumber) +const void* +block_cache_get(void* _cache, off_t blockNumber) { return block_cache_get_etc(_cache, blockNumber, blockNumber, 1); } -/*! - Changes the internal status of a writable block to \a dirty. This can be +/*! Changes the internal status of a writable block to \a dirty. This can be helpful in case you realize you don't need to change that block anymore for whatever reason. @@ -1192,9 +2596,21 @@ block_cache_get(void *_cache, off_t blockNumber) writable! */ status_t -block_cache_set_dirty(void *_cache, off_t blockNumber, bool dirty, +block_cache_set_dirty(void* _cache, off_t blockNumber, bool dirty, int32 transaction) { + block_cache* cache = (block_cache*)_cache; + MutexLocker locker(&cache->lock); + + cached_block* block = (cached_block*)hash_lookup(cache->hash, + &blockNumber); + if (block == NULL) + return B_BAD_VALUE; + if (block->is_dirty == dirty) { + // there is nothing to do for us + return B_OK; + } + // TODO: not yet implemented if (dirty) panic("block_cache_set_dirty(): not yet implemented that way!\n"); @@ -1204,10 +2620,10 @@ block_cache_set_dirty(void *_cache, off_t blockNumber, bool dirty, void -block_cache_put(void *_cache, off_t blockNumber) +block_cache_put(void* _cache, off_t blockNumber) { - block_cache *cache = (block_cache *)_cache; - BenaphoreLocker locker(&cache->lock); + block_cache* cache = (block_cache*)_cache; + MutexLocker locker(&cache->lock); put_cached_block(cache, blockNumber); } diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/haiku_block_cache.h b/src/add-ons/kernel/file_systems/userlandfs/server/haiku_block_cache.h new file mode 100644 index 0000000000..16aa4a06ed --- /dev/null +++ b/src/add-ons/kernel/file_systems/userlandfs/server/haiku_block_cache.h @@ -0,0 +1,22 @@ +/* + * Copyright 2005, Axel Dörfler, axeld@pinc-software.de. + * Distributed under the terms of the MIT License. + */ +#ifndef USERLAND_FS_HAIKU_BLOCK_CACHE_H +#define USERLAND_FS_HAIKU_BLOCK_CACHE_H + + +#include + + +namespace UserlandFS { +namespace HaikuKernelEmu { + +status_t block_cache_init(void); +size_t block_cache_used_memory(); + +} // namespace HaikuKernelEmu +} // namespace UserlandFS + + +#endif // USERLAND_FS_HAIKU_BLOCK_CACHE_H diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/haiku_block_cache_priv.h b/src/add-ons/kernel/file_systems/userlandfs/server/haiku_block_cache_priv.h deleted file mode 100644 index ee25813230..0000000000 --- a/src/add-ons/kernel/file_systems/userlandfs/server/haiku_block_cache_priv.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2004-2006, Axel Dörfler, axeld@pinc-software.de. All rights reserved. - * Distributed under the terms of the MIT License. - */ -#ifndef USERLAND_FS_HAIKU_BLOCK_CACHE_PRIVATE_H -#define USERLAND_FS_HAIKU_BLOCK_CACHE_PRIVATE_H - -#include "haiku_lock.h" - -#include - - -namespace UserlandFS { -namespace HaikuKernelEmu { - -struct hash_table; -struct vm_page; - - -#define DEBUG_CHANGED - - -struct cache_transaction; -struct cached_block; -struct block_cache; -typedef DoublyLinkedListLink block_link; - - -struct cached_block { - cached_block *next; // next in hash - cached_block *transaction_next; - block_link link; - off_t block_number; - void *current_data; - void *original_data; - void *parent_data; -#ifdef DEBUG_CHANGED - void *compare; -#endif - int32 ref_count; - int32 accessed; - bool busy : 1; - bool is_writing : 1; - bool is_dirty : 1; - bool unused : 1; - bool unmapped : 1; - cache_transaction *transaction; - cache_transaction *previous_transaction; - - static int Compare(void *_cacheEntry, const void *_block); - static uint32 Hash(void *_cacheEntry, const void *_block, uint32 range); -}; - -typedef DoublyLinkedList > block_list; - -struct block_cache { - hash_table *hash; - benaphore lock; - int fd; - off_t max_blocks; - size_t block_size; - int32 allocated_block_count; - int32 next_transaction_id; - cache_transaction *last_transaction; - hash_table *transaction_hash; - - block_list unmapped_blocks; - block_list unused_blocks; - - bool read_only; - - block_cache(int fd, off_t numBlocks, size_t blockSize, bool readOnly); - ~block_cache(); - - status_t InitCheck(); - - void RemoveUnusedBlocks(int32 maxAccessed = LONG_MAX, int32 count = LONG_MAX); - void FreeBlock(cached_block *block); - cached_block *NewBlock(off_t blockNumber); - void Free(void *address); - void *Allocate(); - - static void LowMemoryHandler(void *data, int32 level); -}; - -status_t block_cache_init(); - -} // namespace HaikuKernelEmu -} // namespace UserlandFS - -#endif /* USERLAND_FS_HAIKU_BLOCK_CACHE_PRIVATE_H */ diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/haiku_condition_variable.cpp b/src/add-ons/kernel/file_systems/userlandfs/server/haiku_condition_variable.cpp new file mode 100644 index 0000000000..bc6223eb1d --- /dev/null +++ b/src/add-ons/kernel/file_systems/userlandfs/server/haiku_condition_variable.cpp @@ -0,0 +1,263 @@ +/* + * Copyright 2007-2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + +#include "haiku_condition_variable.h" + +#include +#include +#include + +#include + +// libroot +#include + +// system +#include +#include + +#include "kernel_emu.h" +#include "haiku_lock.h" + + +namespace UserlandFS { +namespace HaikuKernelEmu { + +#define STATUS_ADDED 1 +#define STATUS_WAITING 2 + +using UserlandFS::KernelEmu::dprintf; +using UserlandFS::KernelEmu::panic; + + +static const int kConditionVariableHashSize = 512; + + +struct ConditionVariableHashDefinition { + typedef const void* KeyType; + typedef ConditionVariable ValueType; + + size_t HashKey(const void* key) const + { return (size_t)key; } + size_t Hash(ConditionVariable* variable) const + { return (size_t)variable->fObject; } + bool Compare(const void* key, ConditionVariable* variable) const + { return key == variable->fObject; } + HashTableLink* GetLink(ConditionVariable* variable) const + { return variable; } +}; + +typedef OpenHashTable ConditionVariableHash; +static ConditionVariableHash sConditionVariableHash; +static mutex sConditionVariablesLock; +static mutex sThreadsLock; + + +// #pragma mark - ConditionVariableEntry + + +bool +ConditionVariableEntry::Add(const void* object) +{ + ASSERT(object != NULL); + + fThread = find_thread(NULL); + + MutexLocker _(sConditionVariablesLock); + + fVariable = sConditionVariableHash.Lookup(object); + + if (fVariable == NULL) { + fWaitStatus = B_ENTRY_NOT_FOUND; + return false; + } + + fWaitStatus = STATUS_ADDED; + fVariable->fEntries.Add(this); + + return true; +} + + +status_t +ConditionVariableEntry::Wait(uint32 flags, bigtime_t timeout) +{ + MutexLocker conditionLocker(sConditionVariablesLock); + + if (fVariable == NULL) + return fWaitStatus; + + user_thread* userThread = get_user_thread(); + + userThread->wait_status = 1; + fWaitStatus = STATUS_WAITING; + + conditionLocker.Unlock(); + + MutexLocker threadLocker(sThreadsLock); + + status_t error; + if ((flags & (B_RELATIVE_TIMEOUT | B_ABSOLUTE_TIMEOUT)) != 0) + error = _kern_block_thread(flags, timeout); + else + error = _kern_block_thread(0, 0); + threadLocker.Unlock(); + + conditionLocker.Lock(); + + // remove entry from variable, if not done yet + if (fVariable != NULL) { + fVariable->fEntries.Remove(this); + fVariable = NULL; + } + + return error; +} + + +status_t +ConditionVariableEntry::Wait(const void* object, uint32 flags, + bigtime_t timeout) +{ + if (Add(object)) + return Wait(flags, timeout); + return B_ENTRY_NOT_FOUND; +} + + +inline void +ConditionVariableEntry::AddToVariable(ConditionVariable* variable) +{ + fThread = find_thread(NULL); + + MutexLocker _(sConditionVariablesLock); + + fVariable = variable; + fWaitStatus = STATUS_ADDED; + fVariable->fEntries.Add(this); +} + + +// #pragma mark - ConditionVariable + + +/*! Initialization method for anonymous (unpublished) condition variables. +*/ +void +ConditionVariable::Init(const void* object, const char* objectType) +{ + fObject = object; + fObjectType = objectType; + new(&fEntries) EntryList; +} + + +void +ConditionVariable::Publish(const void* object, const char* objectType) +{ + ASSERT(object != NULL); + + fObject = object; + fObjectType = objectType; + new(&fEntries) EntryList; + + MutexLocker locker(sConditionVariablesLock); + + ASSERT(sConditionVariableHash.Lookup(object) == NULL); + + sConditionVariableHash.InsertUnchecked(this); +} + + +void +ConditionVariable::Unpublish(bool threadsLocked) +{ + ASSERT(fObject != NULL); + + MutexLocker threadLocker(threadsLocked ? NULL : &sThreadsLock); + MutexLocker locker(sConditionVariablesLock); + + sConditionVariableHash.RemoveUnchecked(this); + fObject = NULL; + fObjectType = NULL; + + if (!fEntries.IsEmpty()) + _NotifyChecked(true, B_ENTRY_NOT_FOUND); +} + + +void +ConditionVariable::Add(ConditionVariableEntry* entry) +{ + entry->AddToVariable(this); +} + + +status_t +ConditionVariable::Wait(uint32 flags, bigtime_t timeout) +{ + ConditionVariableEntry entry; + Add(&entry); + return entry.Wait(flags, timeout); +} + + +void +ConditionVariable::_Notify(bool all, bool threadsLocked) +{ + MutexLocker threadLocker(threadsLocked ? NULL : &sThreadsLock); + MutexLocker locker(sConditionVariablesLock); + + if (!fEntries.IsEmpty()) + _NotifyChecked(all, B_OK); +} + + +/*! Called with interrupts disabled and the condition variable spinlock and + thread lock held. +*/ +void +ConditionVariable::_NotifyChecked(bool all, status_t result) +{ + // dequeue and wake up the blocked threads + while (ConditionVariableEntry* entry = fEntries.RemoveHead()) { + entry->fVariable = NULL; + + if (entry->fWaitStatus <= 0) + continue; + + if (entry->fWaitStatus == STATUS_WAITING) + _kern_unblock_thread(entry->fThread, result); + + entry->fWaitStatus = result; + + if (!all) + break; + } +} + + +// #pragma mark - + + +status_t +condition_variable_init() +{ + mutex_init(&sConditionVariablesLock, "condition variables"); + mutex_init(&sThreadsLock, "threads"); + + new(&sConditionVariableHash) ConditionVariableHash; + + status_t error = sConditionVariableHash.Init(kConditionVariableHashSize); + if (error != B_OK) { + panic("condition_variable_init(): Failed to init hash table: %s", + strerror(error)); + } + + return error; +} + +} // namespace HaikuKernelEmu +} // namespace UserlandFS diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/haiku_condition_variable.h b/src/add-ons/kernel/file_systems/userlandfs/server/haiku_condition_variable.h new file mode 100644 index 0000000000..29587a713c --- /dev/null +++ b/src/add-ons/kernel/file_systems/userlandfs/server/haiku_condition_variable.h @@ -0,0 +1,101 @@ +/* + * Copyright 2007-2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef USERLAND_FS_HAIKU_CONDITION_VARIABLE_H +#define USERLAND_FS_HAIKU_CONDITION_VARIABLE_H + +#include + +#include +#include + + +namespace UserlandFS { +namespace HaikuKernelEmu { + + +class ConditionVariable; + + +struct ConditionVariableEntry + : DoublyLinkedListLinkImpl { +public: + bool Add(const void* object); + status_t Wait(uint32 flags = 0, bigtime_t timeout = 0); + status_t Wait(const void* object, uint32 flags = 0, + bigtime_t timeout = 0); + + inline ConditionVariable* Variable() const { return fVariable; } + +private: + inline void AddToVariable(ConditionVariable* variable); + +private: + ConditionVariable* fVariable; + thread_id fThread; + status_t fWaitStatus; + + friend class ConditionVariable; +}; + + +class ConditionVariable : protected HashTableLink { +public: + void Init(const void* object, + const char* objectType); + // for anonymous (unpublished) cvars + + void Publish(const void* object, + const char* objectType); + void Unpublish(bool threadsLocked = false); + + inline void NotifyOne(bool threadsLocked = false); + inline void NotifyAll(bool threadsLocked = false); + + void Add(ConditionVariableEntry* entry); + + status_t Wait(uint32 flags = 0, bigtime_t timeout = 0); + // all-in one, i.e. doesn't need a + // ConditionVariableEntry + + const void* Object() const { return fObject; } + const char* ObjectType() const { return fObjectType; } + +private: + void _Notify(bool all, bool threadsLocked); + void _NotifyChecked(bool all, status_t result); + +protected: + typedef DoublyLinkedList EntryList; + + const void* fObject; + const char* fObjectType; + EntryList fEntries; + + friend class ConditionVariableEntry; + friend class ConditionVariableHashDefinition; +}; + + +inline void +ConditionVariable::NotifyOne(bool threadsLocked) +{ + _Notify(false, threadsLocked); +} + + +inline void +ConditionVariable::NotifyAll(bool threadsLocked) +{ + _Notify(true, threadsLocked); +} + + +status_t condition_variable_init(); + + +} // namespace HaikuKernelEmu +} // namespace UserlandFS + +#endif // USERLAND_FS_HAIKU_CONDITION_VARIABLE_H diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/haiku_fs_cache.h b/src/add-ons/kernel/file_systems/userlandfs/server/haiku_fs_cache.h index 352c048f51..e241ac595c 100644 --- a/src/add-ons/kernel/file_systems/userlandfs/server/haiku_fs_cache.h +++ b/src/add-ons/kernel/file_systems/userlandfs/server/haiku_fs_cache.h @@ -18,43 +18,50 @@ class HaikuKernelVolume; namespace HaikuKernelEmu { /* transactions */ -extern int32 cache_start_transaction(void *_cache); -extern status_t cache_sync_transaction(void *_cache, int32 id); -extern status_t cache_end_transaction(void *_cache, int32 id, +extern int32 cache_start_transaction(void *cache); +extern status_t cache_sync_transaction(void *cache, int32 id); +extern status_t cache_end_transaction(void *cache, int32 id, transaction_notification_hook hook, void *data); -extern status_t cache_abort_transaction(void *_cache, int32 id); -extern int32 cache_detach_sub_transaction(void *_cache, int32 id, +extern status_t cache_abort_transaction(void *cache, int32 id); +extern int32 cache_detach_sub_transaction(void *cache, int32 id, transaction_notification_hook hook, void *data); -extern status_t cache_abort_sub_transaction(void *_cache, int32 id); -extern status_t cache_start_sub_transaction(void *_cache, int32 id); -extern status_t cache_next_block_in_transaction(void *_cache, int32 id, - uint32 *_cookie, off_t *_blockNumber, void **_data, - void **_unchangedData); -extern int32 cache_blocks_in_transaction(void *_cache, int32 id); -extern int32 cache_blocks_in_sub_transaction(void *_cache, int32 id); +extern status_t cache_abort_sub_transaction(void *cache, int32 id); +extern status_t cache_start_sub_transaction(void *cache, int32 id); +extern status_t cache_add_transaction_listener(void *cache, int32 id, + int32 events, transaction_notification_hook hook, + void *data); +extern status_t cache_remove_transaction_listener(void *cache, int32 id, + transaction_notification_hook hook, void *data); +extern status_t cache_next_block_in_transaction(void *cache, int32 id, + bool mainOnly, long *_cookie, off_t *_blockNumber, + void **_data, void **_unchangedData); +extern int32 cache_blocks_in_transaction(void *cache, int32 id); +extern int32 cache_blocks_in_main_transaction(void *cache, int32 id); +extern int32 cache_blocks_in_sub_transaction(void *cache, int32 id); /* block cache */ -extern void block_cache_delete(void *_cache, bool allowWrites); +extern void block_cache_delete(void *cache, bool allowWrites); extern void *block_cache_create(int fd, off_t numBlocks, size_t blockSize, bool readOnly); -extern status_t block_cache_sync(void *_cache); -extern status_t block_cache_sync_etc(void *_cache, off_t blockNumber, +extern status_t block_cache_sync(void *cache); +extern status_t block_cache_sync_etc(void *cache, off_t blockNumber, size_t numBlocks); - -extern status_t block_cache_make_writable(void *_cache, off_t blockNumber, +extern void block_cache_discard(void *cache, off_t blockNumber, + size_t numBlocks); +extern status_t block_cache_make_writable(void *cache, off_t blockNumber, int32 transaction); -extern void *block_cache_get_writable_etc(void *_cache, off_t blockNumber, +extern void *block_cache_get_writable_etc(void *cache, off_t blockNumber, off_t base, off_t length, int32 transaction); -extern void *block_cache_get_writable(void *_cache, off_t blockNumber, +extern void *block_cache_get_writable(void *cache, off_t blockNumber, int32 transaction); -extern void *block_cache_get_empty(void *_cache, off_t blockNumber, +extern void *block_cache_get_empty(void *cache, off_t blockNumber, int32 transaction); -extern const void *block_cache_get_etc(void *_cache, off_t blockNumber, +extern const void *block_cache_get_etc(void *cache, off_t blockNumber, off_t base, off_t length); -extern const void *block_cache_get(void *_cache, off_t blockNumber); -extern status_t block_cache_set_dirty(void *_cache, off_t blockNumber, +extern const void *block_cache_get(void *cache, off_t blockNumber); +extern status_t block_cache_set_dirty(void *cache, off_t blockNumber, bool isDirty, int32 transaction); -extern void block_cache_put(void *_cache, off_t blockNumber); +extern void block_cache_put(void *cache, off_t blockNumber); /* file cache */ extern void *file_cache_create(dev_t mountID, ino_t vnodeID, off_t size, diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/haiku_hash.cpp b/src/add-ons/kernel/file_systems/userlandfs/server/haiku_hash.cpp index e35d6d391b..f7e1fe8c99 100644 --- a/src/add-ons/kernel/file_systems/userlandfs/server/haiku_hash.cpp +++ b/src/add-ons/kernel/file_systems/userlandfs/server/haiku_hash.cpp @@ -49,6 +49,27 @@ struct hash_table { #define PUT_IN_NEXT(t, e, val) (*(unsigned long *)NEXT_ADDR(t, e) = (long)(val)) +const uint32 kPrimes [] = { + 13, 31, 61, 127, 251, + 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521, 131071, 262139, + 524287, 1048573, 2097143, 4194301, 8388593, 16777213, 33554393, 67108859, + 134217689, 268435399, 536870909, 1073741789, 2147483647, 0 +}; + + +static uint32 +get_prime_table_size(uint32 size) +{ + int i; + for (i = 0; kPrimes[i] != 0; i++) { + if (kPrimes[i] > size) + return kPrimes[i]; + } + + return kPrimes[i - 1]; +} + + static inline void * next_element(hash_table *table, void *element) { @@ -57,15 +78,59 @@ next_element(hash_table *table, void *element) } +static status_t +hash_grow(struct hash_table *table) +{ + uint32 newSize = get_prime_table_size(table->num_elements); + struct hash_element **newTable; + uint32 index; + + if (table->table_size >= newSize) + return B_OK; + + newTable = (struct hash_element **)malloc(sizeof(void *) * newSize); + if (newTable == NULL) + return B_NO_MEMORY; + + memset(newTable, 0, sizeof(void *) * newSize); + + // rehash all the entries and add them to the new table + for (index = 0; index < table->table_size; index++) { + void *element; + void *next; + + for (element = table->table[index]; element != NULL; element = next) { + uint32 hash = table->hash_func(element, NULL, newSize); + next = NEXT(table, element); + PUT_IN_NEXT(table, element, newTable[hash]); + newTable[hash] = (struct hash_element *)element; + } + } + + free(table->table); + + table->table = newTable; + table->table_size = newSize; + + TRACE(("hash_grow: grown table %p, new size %lu\n", table, newSize)); + return B_OK; +} + + +// #pragma mark - kernel private API + + struct hash_table * -hash_init(uint32 table_size, int next_ptr_offset, - int compare_func(void *e, const void *key), - uint32 hash_func(void *e, const void *key, uint32 range)) +hash_init(uint32 tableSize, int nextPointerOffset, + int compareFunc(void *e, const void *key), + uint32 hashFunc(void *e, const void *key, uint32 range)) { struct hash_table *t; - unsigned int i; + uint32 i; - if (compare_func == NULL || hash_func == NULL) { + tableSize = get_prime_table_size(tableSize); + + if (compareFunc == NULL || hashFunc == NULL) { dprintf("hash_init() called with NULL function pointer\n"); return NULL; } @@ -74,24 +139,24 @@ hash_init(uint32 table_size, int next_ptr_offset, if (t == NULL) return NULL; - t->table = (struct hash_element **)malloc(sizeof(void *) * table_size); + t->table = (struct hash_element **)malloc(sizeof(void *) * tableSize); if (t->table == NULL) { free(t); return NULL; } - for (i = 0; i < table_size; i++) + for (i = 0; i < tableSize; i++) t->table[i] = NULL; - t->table_size = table_size; - t->next_ptr_offset = next_ptr_offset; + t->table_size = tableSize; + t->next_ptr_offset = nextPointerOffset; t->flags = 0; t->num_elements = 0; - t->compare_func = compare_func; - t->hash_func = hash_func; + t->compare_func = compareFunc; + t->hash_func = hashFunc; TRACE(("hash_init: created table %p, next_ptr_offset %d, compare_func %p, hash_func %p\n", - t, next_ptr_offset, compare_func, hash_func)); + t, nextPointerOffset, compareFunc, hashFunc)); return t; } @@ -115,16 +180,36 @@ hash_insert(struct hash_table *table, void *element) uint32 hash; ASSERT(table != NULL && element != NULL); - TRACE(("hash_insert: table 0x%x, element 0x%x\n", table, element)); + TRACE(("hash_insert: table %p, element %p\n", table, element)); hash = table->hash_func(element, NULL, table->table_size); PUT_IN_NEXT(table, element, table->table[hash]); table->table[hash] = (struct hash_element *)element; table->num_elements++; - // ToDo: resize hash table if it's grown too much! + return B_OK; +} - return 0; + +status_t +hash_insert_grow(struct hash_table *table, void *element) +{ + uint32 hash; + + ASSERT(table != NULL && element != NULL); + TRACE(("hash_insert_grow: table %p, element %p\n", table, element)); + + hash = table->hash_func(element, NULL, table->table_size); + PUT_IN_NEXT(table, element, table->table[hash]); + table->table[hash] = (struct hash_element *)element; + table->num_elements++; + + if ((uint32)table->num_elements > table->table_size) { + //dprintf("hash_insert: table has grown too much: %d in %d\n", table->num_elements, (int)table->table_size); + hash_grow(table); + } + + return B_OK; } @@ -157,32 +242,34 @@ hash_remove_current(struct hash_table *table, struct hash_iterator *iterator) { uint32 index = iterator->bucket; void *element; + void *lastElement = NULL; - if (iterator->current == NULL) - panic("hash_remove_current() called too early."); + if (iterator->current == NULL || (element = table->table[index]) == NULL) { + panic("hash_remove_current(): invalid iteration state"); + return; + } - for (element = table->table[index]; index < table->table_size; index++) { - void *lastElement = NULL; + while (element != NULL) { + if (element == iterator->current) { + iterator->current = lastElement; - while (element != NULL) { - if (element == iterator->current) { - iterator->current = lastElement; - - if (lastElement != NULL) { - // connect the previous entry with the next one - PUT_IN_NEXT(table, lastElement, NEXT(table, element)); - } else { - table->table[index] = (struct hash_element *)NEXT(table, - element); - } - - table->num_elements--; - return; + if (lastElement != NULL) { + // connect the previous entry with the next one + PUT_IN_NEXT(table, lastElement, NEXT(table, element)); + } else { + table->table[index] = (struct hash_element *)NEXT(table, + element); } - element = NEXT(table, element); + table->num_elements--; + return; } + + lastElement = element; + element = NEXT(table, element); } + + panic("hash_remove_current(): current element not found!"); } @@ -309,5 +396,48 @@ hash_hash_string(const char *string) return hash; } + +uint32 +hash_count_elements(struct hash_table *table) +{ + return table->num_elements; +} + + +uint32 +hash_count_used_slots(struct hash_table *table) +{ + uint32 usedSlots = 0; + uint32 i; + for (i = 0; i < table->table_size; i++) { + if (table->table[i] != NULL) + usedSlots++; + } + + return usedSlots; +} + + +void +hash_dump_table(struct hash_table* table) +{ + uint32 i; + + dprintf("hash table %p, table size: %lu, elements: %u\n", table, + table->table_size, table->num_elements); + + for (i = 0; i < table->table_size; i++) { + struct hash_element* element = table->table[i]; + if (element != NULL) { + dprintf("%6lu:", i); + while (element != NULL) { + dprintf(" %p", element); + element = (hash_element*)NEXT(table, element); + } + dprintf("\n"); + } + } +} + } // namespace HaikuKernelEmu } // namespace UserlandFS diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/haiku_hash.h b/src/add-ons/kernel/file_systems/userlandfs/server/haiku_hash.h index ee3512f600..7e4b33af58 100644 --- a/src/add-ons/kernel/file_systems/userlandfs/server/haiku_hash.h +++ b/src/add-ons/kernel/file_systems/userlandfs/server/haiku_hash.h @@ -1,7 +1,10 @@ -/* -** Copyright 2001-2002, Travis Geiselbrecht. All rights reserved. -** Distributed under the terms of the NewOS License. -*/ +/* + * Copyright 2002-2008, Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Copyright 2001-2002, Travis Geiselbrecht. All rights reserved. + * Distributed under the terms of the NewOS License. + */ #ifndef USERLAND_FS_HAIKU_HASH_H #define USERLAND_FS_HAIKU_HASH_H @@ -10,6 +13,13 @@ namespace UserlandFS { namespace HaikuKernelEmu { +// The use of offsetof() on non-PODs is invalid. Since many structs use +// templated members (i.e. DoublyLinkedList) which makes them non-PODs we +// can't use offsetof() anymore. This macro does the same, but requires an +// instance of the object in question. +#define offset_of_member(OBJECT, MEMBER) \ + ((size_t)((char*)&OBJECT.MEMBER - (char*)&OBJECT)) + // can be allocated on the stack typedef struct hash_iterator { void *current; @@ -23,6 +33,7 @@ struct hash_table *hash_init(uint32 table_size, int next_ptr_offset, uint32 hash_func(void *element, const void *key, uint32 range)); int hash_uninit(struct hash_table *table); status_t hash_insert(struct hash_table *table, void *_element); +status_t hash_insert_grow(struct hash_table *table, void *_element); status_t hash_remove(struct hash_table *table, void *_element); void hash_remove_current(struct hash_table *table, struct hash_iterator *iterator); void *hash_remove_first(struct hash_table *table, uint32 *_cookie); @@ -32,6 +43,9 @@ struct hash_iterator *hash_open(struct hash_table *table, struct hash_iterator * void hash_close(struct hash_table *table, struct hash_iterator *i, bool free_iterator); void *hash_next(struct hash_table *table, struct hash_iterator *i); void hash_rewind(struct hash_table *table, struct hash_iterator *i); +uint32 hash_count_elements(struct hash_table *table); +uint32 hash_count_used_slots(struct hash_table *table); +void hash_dump_table(struct hash_table* table); /* function pointers must look like this: * diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/haiku_lock.cpp b/src/add-ons/kernel/file_systems/userlandfs/server/haiku_lock.cpp index 472c0cc52c..91d19fcb43 100644 --- a/src/add-ons/kernel/file_systems/userlandfs/server/haiku_lock.cpp +++ b/src/add-ons/kernel/file_systems/userlandfs/server/haiku_lock.cpp @@ -20,33 +20,35 @@ namespace UserlandFS { namespace HaikuKernelEmu { -int32 -recursive_lock_get_recursion(recursive_lock *lock) +sem_id +_init_semaphore(int32 count, const char* name) { - if (lock->holder == find_thread(NULL)) - return lock->recursion; - - return -1; + sem_id sem = create_sem(count, name); + if (sem < 0) + panic("_init_semaphore(): Failed to create semaphore!\n"); + return sem; } -status_t +void recursive_lock_init(recursive_lock *lock, const char *name) +{ + recursive_lock_init_etc(lock, name, 0); +} + + +void +recursive_lock_init_etc(recursive_lock *lock, const char *name, uint32 flags) { if (lock == NULL) - return B_BAD_VALUE; + panic("recursive_lock_init_etc(): NULL lock\n"); if (name == NULL) name = "recursive lock"; lock->holder = -1; lock->recursion = 0; - lock->sem = create_sem(1, name); - - if (lock->sem >= B_OK) - return B_OK; - - return lock->sem; + lock->sem = _init_semaphore(1, name); } @@ -78,6 +80,23 @@ recursive_lock_lock(recursive_lock *lock) } +status_t +recursive_lock_trylock(recursive_lock *lock) +{ + thread_id thread = find_thread(NULL); + + if (thread != lock->holder) { + status_t status = acquire_sem_etc(lock->sem, 1, B_RELATIVE_TIMEOUT, 0); + if (status < B_OK) + return status; + + lock->holder = thread; + } + lock->recursion++; + return B_OK; +} + + void recursive_lock_unlock(recursive_lock *lock) { @@ -91,25 +110,38 @@ recursive_lock_unlock(recursive_lock *lock) } +int32 +recursive_lock_get_recursion(recursive_lock *lock) +{ + if (lock->holder == find_thread(NULL)) + return lock->recursion; + + return -1; +} + + // #pragma mark - -status_t -mutex_init(mutex *m, const char *name) +void +mutex_init(mutex *lock, const char *name) { - if (m == NULL) - return EINVAL; + mutex_init_etc(lock, name, 0); +} + + +void +mutex_init_etc(mutex* lock, const char* name, uint32 flags) +{ + if (lock == NULL) + panic("mutex_init_etc(): NULL lock\n"); if (name == NULL) name = "mutex_sem"; - m->holder = -1; + lock->holder = -1; - m->sem = create_sem(1, name); - if (m->sem >= B_OK) - return B_OK; - - return m->sem; + lock->sem = _init_semaphore(1, name); } @@ -145,6 +177,24 @@ mutex_lock(mutex *mutex) } +status_t +mutex_trylock(mutex *mutex) +{ + thread_id me = find_thread(NULL); + status_t status; + + status = acquire_sem_etc(mutex->sem, 1, B_RELATIVE_TIMEOUT, 0); + if (status < B_OK) + return status; + + if (me == mutex->holder) + panic("mutex_lock failure: mutex %p (sem = 0x%lx) acquired twice by thread 0x%lx\n", mutex, mutex->sem, me); + + mutex->holder = me; + return B_OK; +} + + void mutex_unlock(mutex *mutex) { @@ -163,46 +213,23 @@ mutex_unlock(mutex *mutex) // #pragma mark - -status_t -benaphore_init(benaphore *ben, const char *name) +void +rw_lock_init(rw_lock *lock, const char *name) { - if (ben == NULL || name == NULL) - return B_BAD_VALUE; - - ben->count = 1; - ben->sem = create_sem(0, name); - if (ben->sem >= B_OK) - return B_OK; - - return ben->sem; + rw_lock_init_etc(lock, name, 0); } void -benaphore_destroy(benaphore *ben) -{ - delete_sem(ben->sem); - ben->sem = -1; -} - - -// #pragma mark - - - -status_t -rw_lock_init(rw_lock *lock, const char *name) +rw_lock_init_etc(rw_lock* lock, const char* name, uint32 flags) { if (lock == NULL) - return B_BAD_VALUE; + panic("rw_lock_init_etc(): NULL lock\n"); if (name == NULL) name = "r/w lock"; - lock->sem = create_sem(RW_MAX_READERS, name); - if (lock->sem >= B_OK) - return B_OK; - - return lock->sem; + lock->sem = _init_semaphore(RW_MAX_READERS, name); } diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/haiku_lock.h b/src/add-ons/kernel/file_systems/userlandfs/server/haiku_lock.h index ccd86f392f..bba1f1dd6d 100644 --- a/src/add-ons/kernel/file_systems/userlandfs/server/haiku_lock.h +++ b/src/add-ons/kernel/file_systems/userlandfs/server/haiku_lock.h @@ -20,77 +20,77 @@ typedef struct recursive_lock { int recursion; } recursive_lock; + + typedef struct mutex { sem_id sem; thread_id holder; } mutex; -typedef struct benaphore { - sem_id sem; - int32 count; -} benaphore; +#define MUTEX_FLAG_CLONE_NAME 0x1 + -// Note: this is currently a trivial r/w lock implementation -// it will be replaced with something better later - this -// or a similar API will be made publically available at this point. typedef struct rw_lock { sem_id sem; - int32 count; - benaphore writeLock; } rw_lock; #define RW_MAX_READERS 1000000 +#define RW_LOCK_FLAG_CLONE_NAME 0x1 -extern status_t recursive_lock_init(recursive_lock *lock, const char *name); +#define ASSERT_LOCKED_RECURSIVE(r) do {} while (false) +#define ASSERT_LOCKED_MUTEX(m) do {} while (false) +#define ASSERT_WRITE_LOCKED_RW_LOCK(m) do {} while (false) +#define ASSERT_READ_LOCKED_RW_LOCK(l) do {} while (false) + + +// static initializers +#define MUTEX_INITIALIZER(name) { _init_semaphore(1, name), -1 } +#define RECURSIVE_LOCK_INITIALIZER(name) { _init_semaphore(1, name), -1, 0 } +#define RW_LOCK_INITIALIZER(name) \ + { _init_semaphore(RW_MAX_READERS, name) } + + +sem_id _init_semaphore(int32 count, const char* name); + // implementation private + + +extern void recursive_lock_init(recursive_lock *lock, const char *name); + // name is *not* cloned nor freed in recursive_lock_destroy() +extern void recursive_lock_init_etc(recursive_lock *lock, const char *name, + uint32 flags); extern void recursive_lock_destroy(recursive_lock *lock); extern status_t recursive_lock_lock(recursive_lock *lock); +extern status_t recursive_lock_trylock(recursive_lock *lock); extern void recursive_lock_unlock(recursive_lock *lock); extern int32 recursive_lock_get_recursion(recursive_lock *lock); -extern status_t mutex_init(mutex *m, const char *name); -extern void mutex_destroy(mutex *m); -extern status_t mutex_lock(mutex *m); -extern void mutex_unlock(mutex *m); -extern status_t benaphore_init(benaphore *ben, const char *name); -extern void benaphore_destroy(benaphore *ben); +extern void mutex_init(mutex* lock, const char* name); + // name is *not* cloned nor freed in mutex_destroy() +extern void mutex_init_etc(mutex* lock, const char* name, uint32 flags); +extern void mutex_destroy(mutex* lock); +//extern status_t mutex_switch_lock(mutex* from, mutex* to); + // Unlocks "from" and locks "to" such that unlocking and starting to wait + // for the lock is atomically. I.e. if "from" guards the object "to" belongs + // to, the operation is safe as long as "from" is held while destroying + // "to". -static inline status_t -benaphore_lock_etc(benaphore *ben, uint32 flags, bigtime_t timeout) -{ - if (atomic_add(&ben->count, -1) <= 0) - return acquire_sem_etc(ben->sem, 1, flags, timeout); - - return B_OK; -} +status_t mutex_lock(mutex* lock); +//status_t mutex_lock_threads_locked(mutex* lock); +status_t mutex_trylock(mutex* lock); +void mutex_unlock(mutex* lock); +//void mutex_transfer_lock(mutex* lock, thread_id thread); -static inline status_t -benaphore_lock(benaphore *ben) -{ - if (atomic_add(&ben->count, -1) <= 0) - return acquire_sem(ben->sem); - - return B_OK; -} - - -static inline status_t -benaphore_unlock(benaphore *ben) -{ - if (atomic_add(&ben->count, 1) < 0) - return release_sem(ben->sem); - - return B_OK; -} - -extern status_t rw_lock_init(rw_lock *lock, const char *name); -extern void rw_lock_destroy(rw_lock *lock); -extern status_t rw_lock_read_lock(rw_lock *lock); -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 rw_lock_init(rw_lock* lock, const char* name); + // name is *not* cloned nor freed in rw_lock_destroy() +extern void rw_lock_init_etc(rw_lock* lock, const char* name, uint32 flags); +extern void rw_lock_destroy(rw_lock* lock); +extern status_t rw_lock_read_lock(rw_lock* lock); +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); /* C++ Auto Locking */ @@ -132,28 +132,10 @@ public: // RecursiveLocker typedef AutoLocker RecursiveLocker; -// BenaphoreLocking -class BenaphoreLocking { -public: - inline bool Lock(benaphore *lockable) - { - return benaphore_lock(lockable) == B_OK; - } - - inline void Unlock(benaphore *lockable) - { - benaphore_unlock(lockable); - } -}; - -// BenaphoreLocker -typedef AutoLocker BenaphoreLocker; - } // namespace HaikuKernelEmu } // namespace UserlandFS using UserlandFS::HaikuKernelEmu::MutexLocker; using UserlandFS::HaikuKernelEmu::RecursiveLocker; -using UserlandFS::HaikuKernelEmu::BenaphoreLocker; #endif /* USERLAND_FS_HAIKU_LOCK_H */ diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/haiku_slab.h b/src/add-ons/kernel/file_systems/userlandfs/server/haiku_slab.h new file mode 100644 index 0000000000..541f0388a0 --- /dev/null +++ b/src/add-ons/kernel/file_systems/userlandfs/server/haiku_slab.h @@ -0,0 +1,62 @@ +/* + * Copyright 2008, Axel Dörfler. All Rights Reserved. + * Copyright 2007, Hugo Santos. All Rights Reserved. + * + * Distributed under the terms of the MIT License. + */ +#ifndef USERLAND_FS_HAIKU_SLAB_SLAB_H +#define USERLAND_FS_HAIKU_SLAB_SLAB_H + + +#include + + +namespace UserlandFS { +namespace HaikuKernelEmu { + + +enum { + /* create_object_cache_etc flags */ + CACHE_NO_DEPOT = 1 << 0, + CACHE_UNLOCKED_PAGES = 1 << 1, + CACHE_LARGE_SLAB = 1 << 2, + + /* object_cache_alloc flags */ + CACHE_DONT_SLEEP = 1 << 8, + + /* internal */ + CACHE_DURING_BOOT = 1 << 31 +}; + +typedef struct object_cache object_cache; + +typedef status_t (*object_cache_constructor)(void *cookie, void *object); +typedef void (*object_cache_destructor)(void *cookie, void *object); +typedef void (*object_cache_reclaimer)(void *cookie, int32 level); + + +object_cache *create_object_cache(const char *name, size_t object_size, + size_t alignment, void *cookie, object_cache_constructor constructor, + object_cache_destructor); +object_cache *create_object_cache_etc(const char *name, size_t object_size, + size_t alignment, size_t max_byte_usage, uint32 flags, void *cookie, + object_cache_constructor constructor, object_cache_destructor destructor, + object_cache_reclaimer reclaimer); + +void delete_object_cache(object_cache *cache); + +status_t object_cache_set_minimum_reserve(object_cache *cache, + size_t objectCount); + +void *object_cache_alloc(object_cache *cache, uint32 flags); +void object_cache_free(object_cache *cache, void *object); + +status_t object_cache_reserve(object_cache *cache, size_t object_count, + uint32 flags); + +void object_cache_get_usage(object_cache *cache, size_t *_allocatedMemory); + +} // namespace HaikuKernelEmu +} // namespace UserlandFS + +#endif // USERLAND_FS_HAIKU_SLAB_SLAB_H