* 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:
parent
9467d1448c
commit
96e19c19fc
172
src/system/kernel/cache/block_cache.cpp
vendored
172
src/system/kernel/cache/block_cache.cpp
vendored
@ -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;
|
||||
}
|
||||
}
|
||||
|
214
src/tests/system/kernel/cache/block_cache_test.cpp
vendored
214
src/tests/system/kernel/cache/block_cache_test.cpp
vendored
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user