* The first functions now handle the "discard" flag correctly (namely

cache_end_transaction(), and cache_start_sub_transaction()).
* Further work on the test application, it's now actually usable, first test
  passes.
* dump_block() did erroneously print 'B' for the dirty flag; now both dirty and
  discard have the 'D' (3rd and 5th column).
* block_cache::LowMemoryHandler() is now private (and got an underscore prefix).
* Minor cleanup, shuffled some methods around.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@28508 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Axel Dörfler 2008-11-04 22:40:11 +00:00
parent 9467d1448c
commit 96e19c19fc
2 changed files with 324 additions and 62 deletions

View File

@ -116,23 +116,26 @@ struct block_cache : DoublyLinkedListLinkImpl<block_cache> {
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;
}
}

View File

@ -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;
}