block: cache between one client and one device
This block cache component acts as a block device for a single client. It uses fixed 4K blocks as caching granularity, thereby implicitly reads ahead whenever a client requests lesser amount of blocks. Currently, it only supports a least-recently-used replacement policy. Fixes #113
This commit is contained in:
parent
0bc012eb79
commit
ca513113f6
73
os/run/blk_cache.run
Normal file
73
os/run/blk_cache.run
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
#
|
||||||
|
# \brief Test of Block session interface provided by server/blk_cache
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# Build
|
||||||
|
#
|
||||||
|
build {
|
||||||
|
core init
|
||||||
|
drivers/timer
|
||||||
|
server/blk_cache
|
||||||
|
test/blk
|
||||||
|
}
|
||||||
|
create_boot_directory
|
||||||
|
|
||||||
|
#
|
||||||
|
# Generate config
|
||||||
|
#
|
||||||
|
install_config {
|
||||||
|
<config>
|
||||||
|
<parent-provides>
|
||||||
|
<service name="ROM"/>
|
||||||
|
<service name="RAM"/>
|
||||||
|
<service name="IRQ"/>
|
||||||
|
<service name="IO_MEM"/>
|
||||||
|
<service name="IO_PORT"/>
|
||||||
|
<service name="CAP"/>
|
||||||
|
<service name="PD"/>
|
||||||
|
<service name="RM"/>
|
||||||
|
<service name="CPU"/>
|
||||||
|
<service name="LOG"/>
|
||||||
|
<service name="SIGNAL" />
|
||||||
|
</parent-provides>
|
||||||
|
<default-route>
|
||||||
|
<any-service> <parent/> <any-child/> </any-service>
|
||||||
|
</default-route>
|
||||||
|
<start name="timer">
|
||||||
|
<resource name="RAM" quantum="1M"/>
|
||||||
|
<provides><service name="Timer"/></provides>
|
||||||
|
</start>
|
||||||
|
<start name="test-blk-srv">
|
||||||
|
<resource name="RAM" quantum="10M"/>
|
||||||
|
<provides><service name="Block"/></provides>
|
||||||
|
</start>
|
||||||
|
<start name="blk_cache">
|
||||||
|
<resource name="RAM" quantum="2M" />
|
||||||
|
<provides><service name="Block" /></provides>
|
||||||
|
<route>
|
||||||
|
<service name="Block"><child name="test-blk-srv" /></service>
|
||||||
|
<any-service> <parent /> <any-child /></any-service>
|
||||||
|
</route>
|
||||||
|
</start>
|
||||||
|
<start name="test-blk-cli">
|
||||||
|
<resource name="RAM" quantum="2G" />
|
||||||
|
<route>
|
||||||
|
<service name="Block"><child name="blk_cache" /></service>
|
||||||
|
<any-service> <parent /> <any-child /></any-service>
|
||||||
|
</route>
|
||||||
|
</start>
|
||||||
|
</config> }
|
||||||
|
|
||||||
|
#
|
||||||
|
# Boot modules
|
||||||
|
#
|
||||||
|
build_boot_image { core init timer test-blk-srv blk_cache test-blk-cli }
|
||||||
|
|
||||||
|
#
|
||||||
|
# Qemu
|
||||||
|
#
|
||||||
|
append qemu_args " -nographic -m 64 "
|
||||||
|
|
||||||
|
run_genode_until "Tests finished successfully.*\n" 60
|
||||||
|
puts "Test succeeded"
|
569
os/src/server/blk_cache/chunk.h
Normal file
569
os/src/server/blk_cache/chunk.h
Normal file
|
@ -0,0 +1,569 @@
|
||||||
|
/*
|
||||||
|
* \brief Data structure for storing sparse blocks
|
||||||
|
* \author Norman Feske
|
||||||
|
* \author Stefan Kalkowski
|
||||||
|
* \date 2014-01-06
|
||||||
|
*
|
||||||
|
* Note: originally taken from ram_fs server, and adapted to cache needs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2014 Genode Labs GmbH
|
||||||
|
*
|
||||||
|
* This file is part of the Genode OS framework, which is distributed
|
||||||
|
* under the terms of the GNU General Public License version 2.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _CHUNK_H_
|
||||||
|
#define _CHUNK_H_
|
||||||
|
|
||||||
|
/* Genode includes */
|
||||||
|
#include <util/noncopyable.h>
|
||||||
|
#include <base/allocator.h>
|
||||||
|
#include <base/exception.h>
|
||||||
|
#include <util/list.h>
|
||||||
|
#include <util/string.h>
|
||||||
|
|
||||||
|
namespace Cache {
|
||||||
|
|
||||||
|
typedef Genode::uint64_t offset_t;
|
||||||
|
typedef Genode::uint64_t size_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common base class of both 'Chunk' and 'Chunk_index'
|
||||||
|
*/
|
||||||
|
class Chunk_base : Genode::Noncopyable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
struct Range_exception : Genode::Exception
|
||||||
|
{
|
||||||
|
offset_t off;
|
||||||
|
size_t size;
|
||||||
|
|
||||||
|
Range_exception(offset_t o, size_t s) : off(o), size(s) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef Range_exception Index_out_of_range;
|
||||||
|
typedef Range_exception Range_incomplete;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
offset_t const _base_offset;
|
||||||
|
size_t _num_entries; /* corresponds to last used entry */
|
||||||
|
Chunk_base *_parent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if specified range lies within the chunk
|
||||||
|
*/
|
||||||
|
void assert_valid_range(offset_t start, size_t len,
|
||||||
|
size_t chunk_size) const
|
||||||
|
{
|
||||||
|
if (start < _base_offset)
|
||||||
|
throw Index_out_of_range(start, len);
|
||||||
|
|
||||||
|
if (start + len > _base_offset + chunk_size)
|
||||||
|
throw Index_out_of_range(start, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
Chunk_base(offset_t base_offset, Chunk_base *p)
|
||||||
|
: _base_offset(base_offset), _num_entries(0), _parent(p) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct zero chunk
|
||||||
|
*/
|
||||||
|
Chunk_base() : _base_offset(0), _num_entries(0), _parent(0) { }
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return absolute base offset of chunk in bytes
|
||||||
|
*/
|
||||||
|
offset_t base_offset() const { return _base_offset; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if chunk has no allocated sub chunks
|
||||||
|
*/
|
||||||
|
bool empty() const { return _num_entries == 0; }
|
||||||
|
|
||||||
|
virtual void free(size_t, offset_t) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chunk of bytes used as leaf in hierarchy of chunk indices
|
||||||
|
*/
|
||||||
|
template <unsigned CHUNK_SIZE, typename POLICY>
|
||||||
|
class Chunk : public Chunk_base,
|
||||||
|
public POLICY::Element
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
|
||||||
|
char _data[CHUNK_SIZE];
|
||||||
|
unsigned _writes;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
typedef Range_exception Dirty_chunk;
|
||||||
|
|
||||||
|
static constexpr size_t SIZE = CHUNK_SIZE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct byte chunk
|
||||||
|
*
|
||||||
|
* \param base_offset absolute offset of chunk in bytes
|
||||||
|
*
|
||||||
|
* The first argument is unused. Its mere purpose is to make the
|
||||||
|
* signature of the constructor compatible to the constructor
|
||||||
|
* of 'Chunk_index'.
|
||||||
|
*/
|
||||||
|
Chunk(Genode::Allocator &, offset_t base_offset, Chunk_base *p)
|
||||||
|
: Chunk_base(base_offset, p), _writes(0) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct zero chunk
|
||||||
|
*/
|
||||||
|
Chunk() : _writes(0) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return number of used entries
|
||||||
|
*
|
||||||
|
* The returned value corresponds to the index of the last used
|
||||||
|
* entry + 1. It does not correlate to the number of actually
|
||||||
|
* allocated entries (there may be ranges of zero blocks).
|
||||||
|
*/
|
||||||
|
size_t used_size() const { return _num_entries; }
|
||||||
|
|
||||||
|
void write(char const *src, size_t len, offset_t seek_offset)
|
||||||
|
{
|
||||||
|
assert_valid_range(seek_offset, len, SIZE);
|
||||||
|
|
||||||
|
POLICY::write(this);
|
||||||
|
|
||||||
|
/* offset relative to this chunk */
|
||||||
|
offset_t const local_offset = seek_offset - base_offset();
|
||||||
|
|
||||||
|
Genode::memcpy(&_data[local_offset], src, len);
|
||||||
|
|
||||||
|
_num_entries = Genode::max(_num_entries, local_offset + len);
|
||||||
|
|
||||||
|
_writes++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void read(char *dst, size_t len, offset_t seek_offset) const
|
||||||
|
{
|
||||||
|
assert_valid_range(seek_offset, len, SIZE);
|
||||||
|
|
||||||
|
POLICY::read(this);
|
||||||
|
|
||||||
|
Genode::memcpy(dst, &_data[seek_offset - base_offset()], len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stat(size_t len, offset_t seek_offset) const
|
||||||
|
{
|
||||||
|
assert_valid_range(seek_offset, len, SIZE);
|
||||||
|
|
||||||
|
if (_writes == 0)
|
||||||
|
throw Range_incomplete(base_offset(), SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sync(size_t len, offset_t seek_offset)
|
||||||
|
{
|
||||||
|
if (_writes > 1) {
|
||||||
|
POLICY::sync(this, (char*)_data);
|
||||||
|
_writes = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void alloc(size_t len, offset_t seek_offset) { }
|
||||||
|
|
||||||
|
void truncate(size_t size)
|
||||||
|
{
|
||||||
|
assert_valid_range(size, 0, SIZE);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Offset of the first free position (relative to the beginning
|
||||||
|
* this chunk).
|
||||||
|
*/
|
||||||
|
offset_t const local_offset = size - base_offset();
|
||||||
|
|
||||||
|
if (local_offset >= _num_entries)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Genode::memset(&_data[local_offset], 0, _num_entries - local_offset);
|
||||||
|
|
||||||
|
_num_entries = local_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void free(size_t, offset_t)
|
||||||
|
{
|
||||||
|
if (_writes > 1) throw Dirty_chunk(_base_offset, SIZE);
|
||||||
|
|
||||||
|
_num_entries = 0;
|
||||||
|
if (_parent) _parent->free(SIZE, _base_offset);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template <unsigned NUM_ENTRIES, typename ENTRY_TYPE, typename POLICY>
|
||||||
|
class Chunk_index : public Chunk_base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
typedef ENTRY_TYPE Entry;
|
||||||
|
|
||||||
|
static constexpr size_t ENTRY_SIZE = ENTRY_TYPE::SIZE;
|
||||||
|
static constexpr size_t SIZE = ENTRY_SIZE*NUM_ENTRIES;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Genode::Allocator &_alloc;
|
||||||
|
|
||||||
|
Entry * _entries[NUM_ENTRIES];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return instance of a zero sub chunk
|
||||||
|
*/
|
||||||
|
static Entry &_zero_chunk()
|
||||||
|
{
|
||||||
|
static Entry zero_chunk;
|
||||||
|
return zero_chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return sub chunk at given index
|
||||||
|
*
|
||||||
|
* If there is no sub chunk at the specified index, this function
|
||||||
|
* transparently allocates one. Hence, the returned sub chunk
|
||||||
|
* is ready to be written to.
|
||||||
|
*/
|
||||||
|
Entry &_alloc_entry(unsigned index)
|
||||||
|
{
|
||||||
|
if (index >= NUM_ENTRIES)
|
||||||
|
throw Index_out_of_range(base_offset() + index*ENTRY_SIZE,
|
||||||
|
ENTRY_SIZE);
|
||||||
|
|
||||||
|
if (_entries[index])
|
||||||
|
return *_entries[index];
|
||||||
|
|
||||||
|
offset_t entry_offset = base_offset() + index*ENTRY_SIZE;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
try {
|
||||||
|
_entries[index] = new (&_alloc)
|
||||||
|
Entry(_alloc, entry_offset, this);
|
||||||
|
break;
|
||||||
|
} catch(Genode::Allocator::Out_of_memory) {
|
||||||
|
POLICY::flush(sizeof(Entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_num_entries = Genode::max(_num_entries, index + 1);
|
||||||
|
|
||||||
|
return *_entries[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return sub chunk at given index
|
||||||
|
*/
|
||||||
|
Entry &_entry(unsigned index) const
|
||||||
|
{
|
||||||
|
if (index >= NUM_ENTRIES)
|
||||||
|
throw Index_out_of_range(base_offset() + index*ENTRY_SIZE,
|
||||||
|
ENTRY_SIZE);
|
||||||
|
|
||||||
|
if (_entries[index])
|
||||||
|
return *_entries[index];
|
||||||
|
|
||||||
|
throw Range_incomplete(base_offset() + index*ENTRY_SIZE,
|
||||||
|
ENTRY_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return sub chunk at given index (for syncing only)
|
||||||
|
*
|
||||||
|
* This function transparently provides a zero sub chunk for any
|
||||||
|
* index that is not populated by a real chunk.
|
||||||
|
*/
|
||||||
|
Entry &_entry_for_syncing(unsigned index) const
|
||||||
|
{
|
||||||
|
if (index >= NUM_ENTRIES)
|
||||||
|
throw Index_out_of_range(base_offset() + index*ENTRY_SIZE,
|
||||||
|
ENTRY_SIZE);
|
||||||
|
|
||||||
|
if (_entries[index])
|
||||||
|
return *_entries[index];
|
||||||
|
|
||||||
|
return _zero_chunk();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return index of entry located at specified byte offset
|
||||||
|
*
|
||||||
|
* The caller of this function must make sure that the offset
|
||||||
|
* parameter is within the bounds of the chunk.
|
||||||
|
*/
|
||||||
|
unsigned _index_by_offset(offset_t offset) const
|
||||||
|
{
|
||||||
|
return (offset - base_offset()) / ENTRY_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply operation 'func' to a range of entries
|
||||||
|
*/
|
||||||
|
template <typename THIS, typename DATA, typename FUNC>
|
||||||
|
static void _range_op(THIS &obj, DATA *data, size_t len,
|
||||||
|
offset_t seek_offset, FUNC const &func)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Depending on whether this function is called for reading
|
||||||
|
* (const function) or writing (non-const function), the
|
||||||
|
* operand type is const or non-const Entry. The correct type
|
||||||
|
* is embedded as a trait in the 'FUNC' functor type.
|
||||||
|
*/
|
||||||
|
typedef typename FUNC::Entry Const_qualified_entry;
|
||||||
|
|
||||||
|
obj.assert_valid_range(seek_offset, len, SIZE);
|
||||||
|
|
||||||
|
while (len > 0) {
|
||||||
|
|
||||||
|
unsigned const index = obj._index_by_offset(seek_offset);
|
||||||
|
|
||||||
|
Const_qualified_entry &entry = FUNC::lookup(obj, index);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate byte offset relative to the chunk
|
||||||
|
*
|
||||||
|
* We cannot use 'entry.base_offset()' for this calculation
|
||||||
|
* because in the const case, the lookup might return a
|
||||||
|
* zero chunk, which has no defined base offset. Therefore,
|
||||||
|
* we calculate the base offset via index*ENTRY_SIZE.
|
||||||
|
*/
|
||||||
|
offset_t const local_seek_offset =
|
||||||
|
seek_offset - obj.base_offset() - index*ENTRY_SIZE;
|
||||||
|
|
||||||
|
/* available capacity at 'entry' starting at seek offset */
|
||||||
|
offset_t const capacity = ENTRY_SIZE - local_seek_offset;
|
||||||
|
size_t const curr_len = Genode::min(len, capacity);
|
||||||
|
|
||||||
|
/* apply functor (read or write) to entry */
|
||||||
|
func(entry, data, curr_len, seek_offset);
|
||||||
|
|
||||||
|
/* advance to next entry */
|
||||||
|
len -= curr_len;
|
||||||
|
data += curr_len;
|
||||||
|
seek_offset += curr_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Alloc_func
|
||||||
|
{
|
||||||
|
typedef ENTRY_TYPE Entry;
|
||||||
|
|
||||||
|
static Entry &lookup(Chunk_index &chunk, unsigned i) {
|
||||||
|
return chunk._alloc_entry(i); }
|
||||||
|
|
||||||
|
void operator () (Entry &entry, char const *src, size_t len,
|
||||||
|
offset_t seek_offset) const
|
||||||
|
{
|
||||||
|
entry.alloc(len, seek_offset);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Write_func
|
||||||
|
{
|
||||||
|
typedef ENTRY_TYPE Entry;
|
||||||
|
|
||||||
|
static Entry &lookup(Chunk_index &chunk, unsigned i) {
|
||||||
|
return chunk._entry(i); }
|
||||||
|
|
||||||
|
void operator () (Entry &entry, char const *src, size_t len,
|
||||||
|
offset_t seek_offset) const
|
||||||
|
{
|
||||||
|
entry.write(src, len, seek_offset);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Read_func
|
||||||
|
{
|
||||||
|
typedef ENTRY_TYPE const Entry;
|
||||||
|
|
||||||
|
static Entry &lookup(Chunk_index const &chunk, unsigned i) {
|
||||||
|
return chunk._entry(i); }
|
||||||
|
|
||||||
|
void operator () (Entry &entry, char *dst, size_t len,
|
||||||
|
offset_t seek_offset) const {
|
||||||
|
entry.read(dst, len, seek_offset); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Stat_func
|
||||||
|
{
|
||||||
|
typedef ENTRY_TYPE const Entry;
|
||||||
|
|
||||||
|
static Entry &lookup(Chunk_index const &chunk, unsigned i) {
|
||||||
|
return chunk._entry(i); }
|
||||||
|
|
||||||
|
void operator () (Entry &entry, char*, size_t len,
|
||||||
|
offset_t seek_offset) const {
|
||||||
|
entry.stat(len, seek_offset); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Sync_func
|
||||||
|
{
|
||||||
|
typedef ENTRY_TYPE Entry;
|
||||||
|
|
||||||
|
static Entry &lookup(Chunk_index const &chunk, unsigned i) {
|
||||||
|
return chunk._entry_for_syncing(i); }
|
||||||
|
|
||||||
|
void operator () (Entry &entry, char*, size_t len,
|
||||||
|
offset_t seek_offset) const
|
||||||
|
{
|
||||||
|
entry.sync(len, seek_offset);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void _init_entries()
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < NUM_ENTRIES; i++)
|
||||||
|
_entries[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _destroy_entry(unsigned i)
|
||||||
|
{
|
||||||
|
if (_entries[i] && (i < _num_entries)) {
|
||||||
|
Genode::destroy(&_alloc, _entries[i]);
|
||||||
|
_entries[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* \param alloc allocator to use for allocating sub-chunk
|
||||||
|
* indices and chunks
|
||||||
|
* \param base_offset absolute offset of the chunk in bytes
|
||||||
|
*/
|
||||||
|
Chunk_index(Genode::Allocator &alloc, offset_t base_offset,
|
||||||
|
Chunk_base *p = 0)
|
||||||
|
: Chunk_base(base_offset, p), _alloc(alloc) { _init_entries(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct zero chunk
|
||||||
|
*/
|
||||||
|
Chunk_index() : _alloc(*(Genode::Allocator *)0) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destructor
|
||||||
|
*/
|
||||||
|
~Chunk_index()
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < NUM_ENTRIES; i++)
|
||||||
|
_destroy_entry(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return size of chunk in bytes
|
||||||
|
*
|
||||||
|
* The returned value corresponds to the position after the highest
|
||||||
|
* offset that was written to.
|
||||||
|
*/
|
||||||
|
size_t used_size() const
|
||||||
|
{
|
||||||
|
if (_num_entries == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* size of entries that lie completely within the used range */
|
||||||
|
size_t const size_whole_entries = ENTRY_SIZE*(_num_entries - 1);
|
||||||
|
|
||||||
|
Entry *last_entry = _entries[_num_entries - 1];
|
||||||
|
if (!last_entry)
|
||||||
|
return size_whole_entries;
|
||||||
|
|
||||||
|
return size_whole_entries + last_entry->used_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write data to chunk
|
||||||
|
*/
|
||||||
|
void write(char const *src, size_t len, offset_t seek_offset) {
|
||||||
|
_range_op(*this, src, len, seek_offset, Write_func()); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate needed chunks
|
||||||
|
*/
|
||||||
|
void alloc(size_t len, offset_t seek_offset) {
|
||||||
|
_range_op(*this, (char*)0, len, seek_offset, Alloc_func()); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read data from chunk
|
||||||
|
*/
|
||||||
|
void read(char *dst, size_t len, offset_t seek_offset) const {
|
||||||
|
_range_op(*this, dst, len, seek_offset, Read_func()); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for chunk availability
|
||||||
|
*/
|
||||||
|
void stat(size_t len, offset_t seek_offset) const {
|
||||||
|
_range_op(*this, (char*)0, len, seek_offset, Stat_func()); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronize chunk when dirty
|
||||||
|
*/
|
||||||
|
void sync(size_t len, offset_t seek_offset) const {
|
||||||
|
_range_op(*this, (char*)0, len, seek_offset, Sync_func()); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free chunks
|
||||||
|
*/
|
||||||
|
void free(size_t len, offset_t seek_offset)
|
||||||
|
{
|
||||||
|
unsigned const index = _index_by_offset(seek_offset);
|
||||||
|
Genode::destroy(&_alloc, _entries[index]);
|
||||||
|
_entries[index] = 0;
|
||||||
|
for (unsigned i = 0; i < NUM_ENTRIES; i++)
|
||||||
|
if (_entries[i])
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_parent) _parent->free(SIZE, _base_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncate chunk to specified size in bytes
|
||||||
|
*
|
||||||
|
* This function can be used to shrink a chunk only. Specifying a
|
||||||
|
* 'size' larger than 'used_size' has no effect. The value returned
|
||||||
|
* by 'used_size' refers always to the position of the last byte
|
||||||
|
* written to the chunk.
|
||||||
|
*/
|
||||||
|
void truncate(size_t size)
|
||||||
|
{
|
||||||
|
unsigned const trunc_index = _index_by_offset(size);
|
||||||
|
|
||||||
|
if (trunc_index >= _num_entries)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (unsigned i = trunc_index + 1; i < _num_entries; i++)
|
||||||
|
_destroy_entry(i);
|
||||||
|
|
||||||
|
/* traverse into sub chunks */
|
||||||
|
if (_entries[trunc_index])
|
||||||
|
_entries[trunc_index]->truncate(size);
|
||||||
|
|
||||||
|
_num_entries = trunc_index + 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the truncated at a chunk boundary, we can release the
|
||||||
|
* empty trailing chunk at 'trunc_index'.
|
||||||
|
*/
|
||||||
|
if (_entries[trunc_index] && _entries[trunc_index]->empty()) {
|
||||||
|
_destroy_entry(trunc_index);
|
||||||
|
_num_entries--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* _CHUNK_H_ */
|
429
os/src/server/blk_cache/driver.h
Normal file
429
os/src/server/blk_cache/driver.h
Normal file
|
@ -0,0 +1,429 @@
|
||||||
|
/*
|
||||||
|
* \brief Cache driver
|
||||||
|
* \author Stefan Kalkowski
|
||||||
|
* \date 2013-12-05
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2013 Genode Labs GmbH
|
||||||
|
*
|
||||||
|
* This file is part of the Genode OS framework, which is distributed
|
||||||
|
* under the terms of the GNU General Public License version 2.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <base/printf.h>
|
||||||
|
#include <block_session/connection.h>
|
||||||
|
#include <block/component.h>
|
||||||
|
|
||||||
|
#include "chunk.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache driver used by the generic block driver framework
|
||||||
|
*
|
||||||
|
* \param POLICY the cache replacement policy (e.g. LRU)
|
||||||
|
*/
|
||||||
|
template <typename POLICY>
|
||||||
|
class Driver : public Block::Driver
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class encapsulates requests to the backend device in progress,
|
||||||
|
* and the packets from the client side that triggered the request.
|
||||||
|
*/
|
||||||
|
struct Request : public Genode::List<Request>::Element
|
||||||
|
{
|
||||||
|
Block::Packet_descriptor srv;
|
||||||
|
Block::Packet_descriptor cli;
|
||||||
|
|
||||||
|
Request(Block::Packet_descriptor &s,
|
||||||
|
Block::Packet_descriptor &c)
|
||||||
|
: srv(s), cli(c) {}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* \return true when the given response packet matches
|
||||||
|
* the request send to the backend device
|
||||||
|
*/
|
||||||
|
bool match(const Block::Packet_descriptor& reply) const
|
||||||
|
{
|
||||||
|
return reply.operation() == srv.operation() &&
|
||||||
|
reply.block_number() == srv.block_number() &&
|
||||||
|
reply.block_count() == srv.block_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* \param write whether it's a write or read request
|
||||||
|
* \param nr block number requested
|
||||||
|
* \param cnt number of blocks requested
|
||||||
|
* \return true when the given parameters match
|
||||||
|
* the request send to the backend device
|
||||||
|
*/
|
||||||
|
bool match(const bool write,
|
||||||
|
const Block::sector_t nr,
|
||||||
|
const Genode::size_t cnt) const
|
||||||
|
{
|
||||||
|
Block::Packet_descriptor::Opcode op = write
|
||||||
|
? Block::Packet_descriptor::WRITE
|
||||||
|
: Block::Packet_descriptor::READ;
|
||||||
|
return op == srv.operation() &&
|
||||||
|
nr >= srv.block_number() &&
|
||||||
|
nr+cnt <= srv.block_number()+srv.block_count();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write failed exception at a specific device offset,
|
||||||
|
* can be triggered whenever the backend device is not ready
|
||||||
|
* to proceed
|
||||||
|
*/
|
||||||
|
struct Write_failed : Genode::Exception
|
||||||
|
{
|
||||||
|
Cache::offset_t off;
|
||||||
|
|
||||||
|
Write_failed(Cache::offset_t o) : off(o) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The given policy class is extended by a synchronization routine,
|
||||||
|
* used by the cache chunk structure
|
||||||
|
*/
|
||||||
|
struct Policy : POLICY {
|
||||||
|
static void sync(const typename POLICY::Element *e, char *src); };
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
enum {
|
||||||
|
SLAB_SZ = Block::Session::TX_QUEUE_SIZE*sizeof(Request),
|
||||||
|
CACHE_BLK_SIZE = 4096
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We use five levels of page-table like chunk structure,
|
||||||
|
* thereby we've a maximum device size of 256^4*4096 (LBA48)
|
||||||
|
*/
|
||||||
|
typedef Cache::Chunk<CACHE_BLK_SIZE, Policy> Chunk_level_4;
|
||||||
|
typedef Cache::Chunk_index<256, Chunk_level_4, Policy> Chunk_level_3;
|
||||||
|
typedef Cache::Chunk_index<256, Chunk_level_3, Policy> Chunk_level_2;
|
||||||
|
typedef Cache::Chunk_index<256, Chunk_level_2, Policy> Chunk_level_1;
|
||||||
|
typedef Cache::Chunk_index<256, Chunk_level_1, Policy> Chunk_level_0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
static Driver *_instance; /* singleton instance */
|
||||||
|
|
||||||
|
Genode::Tslab<Request, SLAB_SZ> _r_slab; /* slab for requests */
|
||||||
|
Genode::List<Request> _r_list; /* list of requests */
|
||||||
|
Genode::Allocator_avl _alloc; /* packet allocator */
|
||||||
|
Block::Connection _blk; /* backend device */
|
||||||
|
Block::Session::Operations _ops; /* allowed operations */
|
||||||
|
Genode::size_t _blk_sz; /* block size */
|
||||||
|
Block::sector_t _blk_cnt; /* block count */
|
||||||
|
Chunk_level_0 _cache; /* chunk hierarchy */
|
||||||
|
Genode::Signal_rpc_member<Driver> _source_ack;
|
||||||
|
Genode::Signal_rpc_member<Driver> _source_submit;
|
||||||
|
Genode::Signal_rpc_member<Driver> _yield;
|
||||||
|
|
||||||
|
Driver(Driver const&); /* singleton pattern */
|
||||||
|
Driver& operator=(Driver const&); /* singleton pattern */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return modulus of cache's versus backend device's block size
|
||||||
|
*/
|
||||||
|
inline int _cache_blk_mod() { return CACHE_BLK_SIZE / _blk_sz; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Round off given block number to cache block size granularity
|
||||||
|
*/
|
||||||
|
inline Block::sector_t _cache_blk_round_off(Block::sector_t nr) {
|
||||||
|
return nr - (nr % _cache_blk_mod()); }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Round up given block number to cache block size granularity
|
||||||
|
*/
|
||||||
|
inline Block::sector_t _cache_blk_round_up(Block::sector_t nr) {
|
||||||
|
return (nr % _cache_blk_mod())
|
||||||
|
? nr + _cache_blk_mod() - (nr % _cache_blk_mod())
|
||||||
|
: nr; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Handle response to a single request
|
||||||
|
*
|
||||||
|
* \param srv packet received from the backend device
|
||||||
|
* \param r outstanding request
|
||||||
|
*/
|
||||||
|
inline void _handle_reply(Block::Packet_descriptor &srv, Request *r)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (r->cli.operation() == Block::Packet_descriptor::READ)
|
||||||
|
read(r->cli.block_number(), r->cli.block_count(),
|
||||||
|
session->tx_sink()->packet_content(r->cli),
|
||||||
|
r->cli);
|
||||||
|
else
|
||||||
|
write(r->cli.block_number(), r->cli.block_count(),
|
||||||
|
session->tx_sink()->packet_content(r->cli),
|
||||||
|
r->cli);
|
||||||
|
} catch(Block::Driver::Request_congestion) {
|
||||||
|
PWRN("cli (%lld %zu) srv (%lld %zu)",
|
||||||
|
r->cli.block_number(), r->cli.block_count(),
|
||||||
|
r->srv.block_number(), r->srv.block_count());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Handle acknowledgements from the backend device
|
||||||
|
*/
|
||||||
|
void _ack_avail(unsigned)
|
||||||
|
{
|
||||||
|
while (_blk.tx()->ack_avail()) {
|
||||||
|
Block::Packet_descriptor p = _blk.tx()->get_acked_packet();
|
||||||
|
|
||||||
|
/* when reading, write result into cache */
|
||||||
|
if (p.operation() == Block::Packet_descriptor::READ)
|
||||||
|
_cache.write(_blk.tx()->packet_content(p),
|
||||||
|
p.block_count() * _blk_sz,
|
||||||
|
p.block_number() * _blk_sz);
|
||||||
|
|
||||||
|
/* loop through the list of requests, and ack all related */
|
||||||
|
for (Request *r = _r_list.first(), *r_to_handle = r; r;
|
||||||
|
r_to_handle = r) {
|
||||||
|
r = r->next();
|
||||||
|
if (r_to_handle->match(p)) {
|
||||||
|
_handle_reply(p, r_to_handle);
|
||||||
|
_r_list.remove(r_to_handle);
|
||||||
|
Genode::destroy(&_r_slab, r_to_handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_blk.tx()->release_packet(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Handle that the backend device is ready to receive again
|
||||||
|
*/
|
||||||
|
void _ready_to_submit(unsigned) { }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Setup a request to the backend device
|
||||||
|
*
|
||||||
|
* \param block_number block number offset
|
||||||
|
* \param block_count number of blocks
|
||||||
|
* \param packet original packet request received from the client
|
||||||
|
*/
|
||||||
|
void _request(Block::sector_t block_number,
|
||||||
|
Genode::size_t block_count,
|
||||||
|
Block::Packet_descriptor &packet)
|
||||||
|
{
|
||||||
|
Block::Packet_descriptor p_to_dev;
|
||||||
|
|
||||||
|
try {
|
||||||
|
/* we've to look whether the request is already pending */
|
||||||
|
for (Request *r = _r_list.first(); r; r = r->next()) {
|
||||||
|
if (r->match(false, block_number, block_count)) {
|
||||||
|
_r_list.insert(new (&_r_slab) Request(r->srv, packet));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* it doesn't pay, we've to send a request to the device */
|
||||||
|
if (!_blk.tx()->ready_to_submit()) {
|
||||||
|
PWRN("not ready_to_submit");
|
||||||
|
throw Request_congestion();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* read ahead CACHE_BLK_SIZE */
|
||||||
|
Block::sector_t nr = _cache_blk_round_off(block_number);
|
||||||
|
Genode::size_t cnt = _cache_blk_round_up(block_count +
|
||||||
|
(block_number - nr));
|
||||||
|
|
||||||
|
/* ensure all memory is available before sending the request */
|
||||||
|
_cache.alloc(cnt * _blk_sz, nr * _blk_sz);
|
||||||
|
|
||||||
|
/* construct and send the packet */
|
||||||
|
p_to_dev =
|
||||||
|
Block::Packet_descriptor(_blk.dma_alloc_packet(_blk_sz*cnt),
|
||||||
|
Block::Packet_descriptor::READ,
|
||||||
|
nr, cnt);
|
||||||
|
_r_list.insert(new (&_r_slab) Request(p_to_dev, packet));
|
||||||
|
_blk.tx()->submit_packet(p_to_dev);
|
||||||
|
} catch(Block::Session::Tx::Source::Packet_alloc_failed) {
|
||||||
|
throw Request_congestion();
|
||||||
|
} catch(Genode::Allocator::Out_of_memory) {
|
||||||
|
if (p_to_dev.valid()) /* clean up */
|
||||||
|
_blk.tx()->release_packet(p_to_dev);
|
||||||
|
//TODO
|
||||||
|
throw Request_congestion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Synchronize dirty chunks with backend device
|
||||||
|
*/
|
||||||
|
void _sync()
|
||||||
|
{
|
||||||
|
Cache::offset_t off = 0;
|
||||||
|
Cache::size_t len = _blk_sz * _blk_cnt;
|
||||||
|
|
||||||
|
while (len > 0) {
|
||||||
|
try {
|
||||||
|
_cache.sync(len, off);
|
||||||
|
len = 0;
|
||||||
|
} catch(Write_failed &e) {
|
||||||
|
/**
|
||||||
|
* Write to backend failed when backend device isn't ready
|
||||||
|
* to proceed, so handle signals, until it's ready again
|
||||||
|
*/
|
||||||
|
off = e.off;
|
||||||
|
len = _blk_sz * _blk_cnt - off;
|
||||||
|
Server::wait_and_dispatch_one_signal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check for chunk availability
|
||||||
|
*
|
||||||
|
* \param nr block number offset
|
||||||
|
* \param cnt number of blocks
|
||||||
|
* \param p client side packet, which triggered this operation
|
||||||
|
*/
|
||||||
|
bool _stat(Block::sector_t nr, Genode::size_t cnt,
|
||||||
|
Block::Packet_descriptor &p)
|
||||||
|
{
|
||||||
|
Cache::offset_t off = nr * _blk_sz;
|
||||||
|
Cache::size_t size = cnt * _blk_sz;
|
||||||
|
Cache::offset_t end = off + size;
|
||||||
|
|
||||||
|
try {
|
||||||
|
_cache.stat(size, off);
|
||||||
|
return true;
|
||||||
|
} catch(Cache::Chunk_base::Range_incomplete &e) {
|
||||||
|
off = Genode::max(off, e.off);
|
||||||
|
size = Genode::min(end - off, e.size);
|
||||||
|
_request(off / _blk_sz, size / _blk_sz, p);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Signal handler for yield requests of the parent
|
||||||
|
*/
|
||||||
|
void _parent_yield(unsigned)
|
||||||
|
{
|
||||||
|
using namespace Genode;
|
||||||
|
|
||||||
|
Parent::Resource_args const args = env()->parent()->yield_request();
|
||||||
|
size_t const requested_ram_quota =
|
||||||
|
Arg_string::find_arg(args.string(), "ram_quota").ulong_value(0);
|
||||||
|
|
||||||
|
/* flush the requested amount of RAM from cache */
|
||||||
|
POLICY::flush(requested_ram_quota);
|
||||||
|
env()->parent()->yield_response();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* \param ep server entrypoint
|
||||||
|
*/
|
||||||
|
Driver(Server::Entrypoint &ep)
|
||||||
|
: _r_slab(Genode::env()->heap()),
|
||||||
|
_alloc(Genode::env()->heap()),
|
||||||
|
_blk(&_alloc, Block::Session::TX_QUEUE_SIZE*CACHE_BLK_SIZE),
|
||||||
|
_blk_sz(0),
|
||||||
|
_blk_cnt(0),
|
||||||
|
_cache(*Genode::env()->heap(), 0),
|
||||||
|
_source_ack(ep, *this, &Driver::_ack_avail),
|
||||||
|
_source_submit(ep, *this, &Driver::_ready_to_submit),
|
||||||
|
_yield(ep, *this, &Driver::_parent_yield)
|
||||||
|
{
|
||||||
|
_blk.info(&_blk_cnt, &_blk_sz, &_ops);
|
||||||
|
_blk.tx_channel()->sigh_ack_avail(_source_ack);
|
||||||
|
_blk.tx_channel()->sigh_ready_to_submit(_source_submit);
|
||||||
|
Genode::env()->parent()->yield_sigh(_yield);
|
||||||
|
|
||||||
|
if (CACHE_BLK_SIZE % _blk_sz) {
|
||||||
|
PERR("only devices that block size is divider of %x supported",
|
||||||
|
CACHE_BLK_SIZE);
|
||||||
|
throw Io_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* truncate chunk structure to real size of the device */
|
||||||
|
_cache.truncate(_blk_sz*_blk_cnt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
~Driver()
|
||||||
|
{
|
||||||
|
/* when session gets closed, synchronize and flush the cache */
|
||||||
|
_sync();
|
||||||
|
POLICY::flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Driver* instance(Server::Entrypoint &ep) {
|
||||||
|
_instance = new (Genode::env()->heap()) Driver(ep);
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Driver* instance() { return _instance; }
|
||||||
|
|
||||||
|
static void destroy()
|
||||||
|
{
|
||||||
|
Genode::destroy(Genode::env()->heap(), _instance);
|
||||||
|
_instance = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Block::Session_client* blk() { return &_blk; }
|
||||||
|
Genode::size_t blk_sz() { return _blk_sz; }
|
||||||
|
|
||||||
|
|
||||||
|
/****************************
|
||||||
|
** Block-driver interface **
|
||||||
|
****************************/
|
||||||
|
|
||||||
|
Genode::size_t block_size() { return _blk_sz; }
|
||||||
|
Block::sector_t block_count() { return _blk_cnt; }
|
||||||
|
Block::Session::Operations ops() { return _ops; }
|
||||||
|
|
||||||
|
void read(Block::sector_t block_number,
|
||||||
|
Genode::size_t block_count,
|
||||||
|
char* buffer,
|
||||||
|
Block::Packet_descriptor &packet)
|
||||||
|
{
|
||||||
|
if (!_ops.supported(Block::Packet_descriptor::READ))
|
||||||
|
throw Io_error();
|
||||||
|
|
||||||
|
if (!_stat(block_number, block_count, packet))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_cache.read(buffer, block_count*_blk_sz, block_number*_blk_sz);
|
||||||
|
session->ack_packet(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(Block::sector_t block_number,
|
||||||
|
Genode::size_t block_count,
|
||||||
|
const char * buffer,
|
||||||
|
Block::Packet_descriptor &packet)
|
||||||
|
{
|
||||||
|
if (!_ops.supported(Block::Packet_descriptor::WRITE))
|
||||||
|
throw Io_error();
|
||||||
|
|
||||||
|
_cache.alloc(block_count * _blk_sz, block_number * _blk_sz);
|
||||||
|
|
||||||
|
if ((block_number % _cache_blk_mod()) && _stat(block_number, 1, packet))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (((block_number+block_count) % _cache_blk_mod())
|
||||||
|
&& _stat(block_number+block_count-1, 1, packet))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_cache.write(buffer, block_count * _blk_sz,
|
||||||
|
block_number * _blk_sz);
|
||||||
|
session->ack_packet(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sync() { _sync(); }
|
||||||
|
};
|
62
os/src/server/blk_cache/lru.cc
Normal file
62
os/src/server/blk_cache/lru.cc
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* \brief Least-recently-used cache replacement strategy
|
||||||
|
* \author Stefan Kalkowski
|
||||||
|
* \date 2013-12-05
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2013 Genode Labs GmbH
|
||||||
|
*
|
||||||
|
* This file is part of the Genode OS framework, which is distributed
|
||||||
|
* under the terms of the GNU General Public License version 2.
|
||||||
|
*/
|
||||||
|
#include <base/printf.h>
|
||||||
|
#include "lru.h"
|
||||||
|
#include "driver.h"
|
||||||
|
|
||||||
|
typedef Driver<Lru_policy>::Chunk_level_4 Chunk;
|
||||||
|
|
||||||
|
static const Lru_policy::Element *lru = 0;
|
||||||
|
static Genode::List<Lru_policy::Element> lru_list;
|
||||||
|
|
||||||
|
|
||||||
|
static void lru_access(const Lru_policy::Element *e)
|
||||||
|
{
|
||||||
|
if (e == lru) return;
|
||||||
|
|
||||||
|
if (e->next()) lru_list.remove(e);
|
||||||
|
|
||||||
|
lru_list.insert(e, lru);
|
||||||
|
lru = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Lru_policy::read(const Lru_policy::Element *e) {
|
||||||
|
lru_access(e); }
|
||||||
|
|
||||||
|
|
||||||
|
void Lru_policy::write(const Lru_policy::Element *e) {
|
||||||
|
lru_access(e); }
|
||||||
|
|
||||||
|
|
||||||
|
void Lru_policy::flush(Cache::size_t size)
|
||||||
|
{
|
||||||
|
Cache::size_t s = 0;
|
||||||
|
for (Lru_policy::Element *e = lru_list.first();
|
||||||
|
e && ((size == 0) || (s < size));
|
||||||
|
e = lru_list.first(), s += sizeof(Chunk)) {
|
||||||
|
Chunk *cb = static_cast<Chunk*>(e);
|
||||||
|
e = e->next();
|
||||||
|
try {
|
||||||
|
cb->free(Driver<Lru_policy>::CACHE_BLK_SIZE,
|
||||||
|
cb->base_offset());
|
||||||
|
lru_list.remove(cb);
|
||||||
|
} catch(Chunk::Dirty_chunk &e) {
|
||||||
|
cb->sync(e.size, e.off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lru_list.first()) lru = 0;
|
||||||
|
|
||||||
|
if (s < size) throw Block::Driver::Request_congestion();
|
||||||
|
}
|
25
os/src/server/blk_cache/lru.h
Normal file
25
os/src/server/blk_cache/lru.h
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* \brief Least-recently-used cache replacement strategy
|
||||||
|
* \author Stefan Kalkowski
|
||||||
|
* \date 2013-12-05
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2013 Genode Labs GmbH
|
||||||
|
*
|
||||||
|
* This file is part of the Genode OS framework, which is distributed
|
||||||
|
* under the terms of the GNU General Public License version 2.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <util/list.h>
|
||||||
|
|
||||||
|
#include "chunk.h"
|
||||||
|
|
||||||
|
struct Lru_policy
|
||||||
|
{
|
||||||
|
class Element : public Genode::List<Element>::Element {};
|
||||||
|
|
||||||
|
static void read(const Element *e);
|
||||||
|
static void write(const Element *e);
|
||||||
|
static void flush(Cache::size_t size = 0);
|
||||||
|
};
|
84
os/src/server/blk_cache/main.cc
Normal file
84
os/src/server/blk_cache/main.cc
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* \brief Cache a block device
|
||||||
|
* \author Stefan Kalkowski
|
||||||
|
* \date 2013-12-05
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2013 Genode Labs GmbH
|
||||||
|
*
|
||||||
|
* This file is part of the Genode OS framework, which is distributed
|
||||||
|
* under the terms of the GNU General Public License version 2.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <os/server.h>
|
||||||
|
|
||||||
|
#include "lru.h"
|
||||||
|
#include "driver.h"
|
||||||
|
|
||||||
|
|
||||||
|
template <typename POLICY> Driver<POLICY>* Driver<POLICY>::_instance = 0;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronize a chunk with the backend device
|
||||||
|
*/
|
||||||
|
template <typename POLICY>
|
||||||
|
void Driver<POLICY>::Policy::sync(const typename POLICY::Element *e, char *dst)
|
||||||
|
{
|
||||||
|
Cache::offset_t off =
|
||||||
|
static_cast<const Driver<POLICY>::Chunk_level_4*>(e)->base_offset();
|
||||||
|
|
||||||
|
if (!Driver::instance()->blk()->tx()->ready_to_submit())
|
||||||
|
throw Write_failed(off);
|
||||||
|
try {
|
||||||
|
Block::Packet_descriptor
|
||||||
|
p(Driver::instance()->blk()->dma_alloc_packet(Driver::CACHE_BLK_SIZE),
|
||||||
|
Block::Packet_descriptor::WRITE,
|
||||||
|
off / Driver::instance()->blk_sz(),
|
||||||
|
Driver::CACHE_BLK_SIZE / Driver::instance()->blk_sz());
|
||||||
|
Driver::instance()->blk()->tx()->submit_packet(p);
|
||||||
|
} catch(Block::Session::Tx::Source::Packet_alloc_failed) {
|
||||||
|
throw Write_failed(off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct Main
|
||||||
|
{
|
||||||
|
Server::Entrypoint &ep;
|
||||||
|
|
||||||
|
struct Factory : Block::Driver_factory
|
||||||
|
{
|
||||||
|
Server::Entrypoint &ep;
|
||||||
|
|
||||||
|
Factory(Server::Entrypoint &ep) : ep(ep) {}
|
||||||
|
|
||||||
|
Block::Driver *create() { return Driver<Lru_policy>::instance(ep); }
|
||||||
|
void destroy(Block::Driver *driver) { Driver<Lru_policy>::destroy(); }
|
||||||
|
} factory;
|
||||||
|
|
||||||
|
void resource_handler(unsigned) { }
|
||||||
|
|
||||||
|
Block::Root root;
|
||||||
|
Server::Signal_rpc_member<Main> resource_dispatcher;
|
||||||
|
|
||||||
|
Main(Server::Entrypoint &ep)
|
||||||
|
: ep(ep), factory(ep), root(ep, Genode::env()->heap(), factory),
|
||||||
|
resource_dispatcher(ep, *this, &Main::resource_handler)
|
||||||
|
{
|
||||||
|
Genode::env()->parent()->announce(ep.manage(root));
|
||||||
|
Genode::env()->parent()->resource_avail_sigh(resource_dispatcher);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/************
|
||||||
|
** Server **
|
||||||
|
************/
|
||||||
|
|
||||||
|
namespace Server {
|
||||||
|
char const *name() { return "blk_cache_ep"; }
|
||||||
|
size_t stack_size() { return 2*1024*sizeof(long); }
|
||||||
|
void construct(Entrypoint &ep) { static Main server(ep); }
|
||||||
|
}
|
3
os/src/server/blk_cache/target.mk
Normal file
3
os/src/server/blk_cache/target.mk
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
TARGET = blk_cache
|
||||||
|
LIBS = base server
|
||||||
|
SRC_CC = main.cc lru.cc
|
|
@ -37,3 +37,4 @@ resource_yield
|
||||||
gdb_monitor
|
gdb_monitor
|
||||||
part_blk
|
part_blk
|
||||||
xml_generator
|
xml_generator
|
||||||
|
blk_cache
|
||||||
|
|
Loading…
Reference in New Issue
Block a user