mirror of
https://github.com/genodelabs/genode.git
synced 2025-04-07 11:27:29 +00:00
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
|
||||
part_blk
|
||||
xml_generator
|
||||
blk_cache
|
||||
|
Loading…
x
Reference in New Issue
Block a user