595 lines
14 KiB
C++
595 lines
14 KiB
C++
/*
|
|
* \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-2017 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU Affero General Public License version 3.
|
|
*/
|
|
|
|
#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
|
|
{
|
|
private:
|
|
|
|
/*
|
|
* Noncopyable
|
|
*/
|
|
Chunk_base(Chunk_base const &);
|
|
Chunk_base &operator = (Chunk_base const &);
|
|
|
|
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;
|
|
bool const _zero; /* marks zero chunk */
|
|
|
|
/**
|
|
* 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),
|
|
_zero(false) { }
|
|
|
|
/**
|
|
* Construct zero chunk
|
|
*/
|
|
Chunk_base()
|
|
: _base_offset(0), _num_entries(0), _parent(0), _zero(true) { }
|
|
|
|
public:
|
|
|
|
virtual ~Chunk_base() { }
|
|
|
|
/**
|
|
* 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; }
|
|
|
|
/**
|
|
* Return true if this is an unused 'zero' chunk
|
|
*/
|
|
bool zero() const { return _zero; }
|
|
|
|
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:
|
|
|
|
/*
|
|
* Noncopyable
|
|
*/
|
|
Chunk_index(Chunk_index const &);
|
|
Chunk_index &operator = (Chunk_index const &);
|
|
|
|
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 {
|
|
if (zero()) return;
|
|
_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_ */
|