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:
Stefan Kalkowski 2014-02-05 14:22:51 +01:00 committed by Norman Feske
parent 0bc012eb79
commit ca513113f6
8 changed files with 1246 additions and 0 deletions

73
os/run/blk_cache.run Normal file
View 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"

View 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_ */

View 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(); }
};

View 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();
}

View 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);
};

View 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); }
}

View File

@ -0,0 +1,3 @@
TARGET = blk_cache
LIBS = base server
SRC_CC = main.cc lru.cc

View File

@ -37,3 +37,4 @@ resource_yield
gdb_monitor
part_blk
xml_generator
blk_cache