diff --git a/src/system/kernel/cache/block_cache.cpp b/src/system/kernel/cache/block_cache.cpp index 15516c60c7..6902134302 100644 --- a/src/system/kernel/cache/block_cache.cpp +++ b/src/system/kernel/cache/block_cache.cpp @@ -116,23 +116,26 @@ struct block_cache : DoublyLinkedListLinkImpl { NotificationList pending_notifications; ConditionVariable condition_variable; - block_cache(int fd, off_t numBlocks, size_t blockSize, bool readOnly); - ~block_cache(); + block_cache(int fd, off_t numBlocks, size_t blockSize, + bool readOnly); + ~block_cache(); - status_t Init(); + status_t Init(); - void RemoveUnusedBlocks(int32 maxAccessed = LONG_MAX, - int32 count = LONG_MAX); - void RemoveBlock(cached_block* block); - void FreeBlock(cached_block* block); - cached_block* NewBlock(off_t blockNumber); - void Free(void* buffer); - void* Allocate(); + void Free(void* buffer); + void* Allocate(); + void RemoveUnusedBlocks(int32 maxAccessed = LONG_MAX, + int32 count = LONG_MAX); + void RemoveBlock(cached_block* block); + void DiscardBlock(cached_block* block); + void FreeBlock(cached_block* block); + cached_block* NewBlock(off_t blockNumber); - static void LowMemoryHandler(void* data, uint32 resources, int32 level); private: - cached_block* _GetUnusedBlock(); + static void _LowMemoryHandler(void* data, uint32 resources, + int32 level); + cached_block* _GetUnusedBlock(); }; struct cache_listener; @@ -777,7 +780,7 @@ block_cache::block_cache(int _fd, off_t numBlocks, size_t blockSize, /*! Should be called with the cache's lock held. */ block_cache::~block_cache() { - unregister_low_resource_handler(&block_cache::LowMemoryHandler, this); + unregister_low_resource_handler(&_LowMemoryHandler, this); hash_uninit(transaction_hash); hash_uninit(hash); @@ -809,7 +812,7 @@ block_cache::Init() if (transaction_hash == NULL) return B_NO_MEMORY; - return register_low_resource_handler(&block_cache::LowMemoryHandler, this, + return register_low_resource_handler(&_LowMemoryHandler, this, B_KERNEL_RESOURCE_PAGES | B_KERNEL_RESOURCE_MEMORY, 0); } @@ -847,36 +850,6 @@ block_cache::FreeBlock(cached_block* 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 - iterator.Remove(); - hash_remove(hash, 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); - - return block; - } - - return NULL; -} - - /*! Allocates a new block for \a blockNumber, ready for use */ cached_block* block_cache::NewBlock(off_t blockNumber) @@ -920,14 +893,6 @@ block_cache::NewBlock(off_t blockNumber) } -void -block_cache::RemoveBlock(cached_block* block) -{ - hash_remove(hash, block); - FreeBlock(block); -} - - void block_cache::RemoveUnusedBlocks(int32 maxAccessed, int32 count) { @@ -957,7 +922,37 @@ block_cache::RemoveUnusedBlocks(int32 maxAccessed, int32 count) void -block_cache::LowMemoryHandler(void* data, uint32 resources, int32 level) +block_cache::RemoveBlock(cached_block* block) +{ + hash_remove(hash, block); + FreeBlock(block); +} + + +/*! 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); + + 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); +} + + +void +block_cache::_LowMemoryHandler(void* data, uint32 resources, int32 level) { block_cache* cache = (block_cache*)data; MutexLocker locker(&cache->lock); @@ -998,6 +993,36 @@ block_cache::LowMemoryHandler(void* data, uint32 resources, int32 level) } +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 + iterator.Remove(); + hash_remove(hash, 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); + + return block; + } + + return NULL; +} + + // #pragma mark - private block functions @@ -1339,12 +1364,13 @@ write_cached_block(block_cache* cache, cached_block* block, static void dump_block(cached_block* block) { - kprintf("%08lx %9Ld %08lx %08lx %08lx %5ld %6ld %c%c%c%c %08lx " - "%08lx\n", (addr_t)block, block->block_number, + 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 ? 'B' : '-', block->unused ? 'U' : '-', + block->is_dirty ? 'D' : '-', block->unused ? 'U' : '-', + block->discard ? 'D' : '-', (addr_t)block->transaction, (addr_t)block->previous_transaction); } @@ -1473,6 +1499,7 @@ dump_cache(int argc, char** argv) uint32 referenced = 0; uint32 count = 0; uint32 dirty = 0; + uint32 discarded = 0; hash_iterator iterator; hash_open(cache->hash, &iterator); cached_block* block; @@ -1482,13 +1509,16 @@ dump_cache(int argc, char** argv) if (block->is_dirty) dirty++; + if (block->discard) + discarded++; if (block->ref_count) referenced++; count++; } - kprintf(" %ld blocks total, %ld dirty, %ld referenced, %ld in unused.\n", count, dirty, - referenced, cache->unused_blocks.Size()); + 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; @@ -1937,6 +1967,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); @@ -2175,10 +2211,21 @@ cache_start_sub_transaction(void* _cache, int32 id) // move all changed blocks up to the parent 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); + continue; + } if (transaction->has_sub_transaction && block->parent_data != NULL && block->parent_data != block->current_data) { @@ -2190,6 +2237,7 @@ 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 @@ -2483,14 +2531,14 @@ block_cache_discard(void* _cache, off_t blockNumber, size_t numBlocks) 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 { - // mark them as discarded (in the current transaction only, if any) - if (block->previous_transaction != NULL) - write_cached_block(cache, block); - + // mark it as discarded (in the current transaction only, if any) block->discard = true; } } diff --git a/src/tests/system/kernel/cache/block_cache_test.cpp b/src/tests/system/kernel/cache/block_cache_test.cpp index b25784a4a6..bf9c2a4dda 100644 --- a/src/tests/system/kernel/cache/block_cache_test.cpp +++ b/src/tests/system/kernel/cache/block_cache_test.cpp @@ -3,11 +3,225 @@ * Distributed under the terms of the MIT License. */ + +#define write_pos block_cache_write_pos +#define read_pos block_cache_read_pos + #include "block_cache.cpp" +#undef write_pos +#undef read_pos + + +#define MAX_BLOCKS 100 + +#define TEST_BLOCK_DATA(block, number, type) \ + if ((block)->type ## _data != NULL && gBlocks[(number)]. type == 0) \ + error("Block %Ld: " #type " should be NULL!", (number)); \ + if ((block)->type ## _data != NULL && gBlocks[(number)]. type != 0 \ + && *(int32*)(block)->type ## _data != gBlocks[(number)]. type) { \ + error("Block %Ld: " #type " wrong (%ld should be %ld)!", (number), \ + *(int32*)(block)->type ## _data, gBlocks[(number)]. type); \ + } + +struct test_block { + int32 current; + int32 original; + int32 parent; + int32 previous_transaction; + int32 transaction; + bool unused; + bool is_dirty; + bool discard; + + bool write; + + bool read; + bool written; + bool present; +}; + +test_block gBlocks[MAX_BLOCKS]; +block_cache* gCache; +size_t gBlockSize; +int32 gTest; +const char* gTestName; + + +ssize_t +block_cache_write_pos(int fd, off_t offset, const void* buffer, size_t size) +{ + //printf("write: %Ld, %p, %lu\n", offset, buffer, size); + gBlocks[offset / gBlockSize].written = true; + return size; +} + + +ssize_t +block_cache_read_pos(int fd, off_t offset, void* buffer, size_t size) +{ + //printf("read: %Ld, %p, %lu\n", offset, buffer, size); + memset(buffer, 0xcc, size); + int32* value = (int32*)buffer; + *value = offset / gBlockSize + 1; + gBlocks[offset / gBlockSize].read = true; + + return size; +} + + +void +init_test_blocks() +{ + memset(gBlocks, 0, sizeof(test_block) * MAX_BLOCKS); + + for (uint32 i = 0; i < MAX_BLOCKS; i++) { + gBlocks[i].current = i + 1; + gBlocks[i].unused = true; + } +} + + +void +error(const char* format, ...) +{ + va_list args; + va_start(args, format); + + fprintf(stderr, "ERROR: "); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + + va_end(args); + + char cacheString[32]; + sprintf(cacheString, "%p", gCache); + char* argv[4]; + argv[0] = "dump"; + argv[1] = "-bt"; + argv[2] = cacheString; + argv[3] = NULL; + dump_cache(3, argv); + + exit(1); +} + + +void +test_blocks(off_t number, int32 count) +{ + for (int32 i = 0; i < count; i++, number++) { + MutexLocker locker(&gCache->lock); + + cached_block* block = (cached_block*)hash_lookup(gCache->hash, &number); + if (block == NULL) { + if (gBlocks[number].present) + error("Block %Ld not found!", number); + continue; + } + if (!gBlocks[number].present) + error("Block %Ld is present, but should not!", number); + + if (block->is_dirty != gBlocks[number].is_dirty) { + error("Block %Ld: dirty bit differs (%d should be %d)!", number, + block->is_dirty, gBlocks[number].is_dirty); + } +#if 0 + if (block->unused != gBlocks[number].unused) { + error("Block %ld: discard bit differs (%d should be %d)!", number, + block->unused, gBlocks[number].unused); + } +#endif + if (block->discard != gBlocks[number].discard) { + error("Block %Ld: discard bit differs (%d should be %d)!", number, + block->discard, gBlocks[number].discard); + } + if (gBlocks[number].write && !gBlocks[number].written) + error("Block %Ld: has not been written yet!", number); + + TEST_BLOCK_DATA(block, number, current); + TEST_BLOCK_DATA(block, number, original); + TEST_BLOCK_DATA(block, number, parent); + } +} + + +void +test_block(off_t block) +{ + test_blocks(block, 1); +} + + +void +stop_test(void) +{ + if (gCache == NULL) + return; + test_blocks(0, MAX_BLOCKS); + + block_cache_delete(gCache, true); +} + + +void +start_test(int32 test, const char* name, bool init = false) +{ + if (init) { + stop_test(); + + gBlockSize = 2048; + gCache = (block_cache*)block_cache_create(-1, MAX_BLOCKS, gBlockSize, + false); + + init_test_blocks(); + } + + gTest = test; + gTestName = name; + printf("----------- Test %ld%s%s -----------\n", gTest, + gTestName[0] ? " - " : "", gTestName); +} + int main(int argc, char** argv) { + block_cache_init(); + + const void* block; + + // TODO: test transaction-less block caches + // TODO: test read-only block caches + + // Test transactions and block caches + + start_test(1, "Discard simple", true); + + int32 id = cache_start_transaction(gCache); + + gBlocks[0].present = true; + gBlocks[0].read = true; + + block = block_cache_get(gCache, 0); + block_cache_put(gCache, 0); + + gBlocks[1].present = true; + gBlocks[1].read = true; + gBlocks[1].write = true; + + block = block_cache_get_writable(gCache, 1, id); + block_cache_put(gCache, 1); + + gBlocks[2].present = false; + + block = block_cache_get_empty(gCache, 2, id); + block_cache_discard(gCache, 2, 1); + block_cache_put(gCache, 2); + + cache_end_transaction(gCache, id, NULL, NULL); + cache_sync_transaction(gCache, id); + + stop_test(); return 0; }