#ifndef BLOCK_CACHE_H #define BLOCK_CACHE_H #include "base/container_of.h" #include "base/file_utils.h" #include "block-cache/io_engine.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace bi = boost::intrusive; //---------------------------------------------------------------- namespace bcache { typedef uint64_t block_address; class validator { public: typedef std::shared_ptr ptr; virtual ~validator() {} virtual void check(void const *data, block_address location) const = 0; virtual bool check_raw(void const *data) const = 0; virtual void prepare(void *data, block_address location) const = 0; }; class noop_validator : public validator { public: void check(void const *data, block_address location) const {} bool check_raw(void const *data) const {return true;} void prepare(void *data, block_address location) const {} }; //---------------------------------------------------------------- class block_cache : private boost::noncopyable { public: enum block_flags { BF_IO_PENDING = (1 << 0), BF_DIRTY = (1 << 1), BF_FLUSH = (1 << 2), }; class block : private boost::noncopyable { public: block() : v_() { } bool operator <(block const &rhs) const { return index_ > rhs.index_; } bool operator ==(block const &rhs) const { return index_ == rhs.index_; } // Do not give this class a destructor, it wont get // called because we manage allocation ourselves. uint64_t get_index() const { return index_; } void *get_data() const { return data_; } void mark_dirty() { set_flags(BF_DIRTY); } void set_flags(unsigned flags) { flags_ |= flags; } unsigned test_flags(unsigned flags) const { return flags_ & flags; } void clear_flags(unsigned flags) { flags_ &= ~flags; } void get() { ref_count_++; }; void put() { if (!ref_count_) throw std::runtime_error("bad put"); if (!--ref_count_) bc_->release(*this); } void unlink_set() { set_hook_.unlink(); } void unlink() { list_hook_.unlink(); } private: friend class block_cache; friend class cmp_index; block_cache *bc_; uint64_t index_; void *data_; bi::list_member_hook> list_hook_; bi::set_member_hook> set_hook_; unsigned ref_count_; int error_; unsigned flags_; iocb control_block_; validator::ptr v_; }; struct cmp_index { bool operator()(block_address index, block const &b) const { return index > b.index_; } bool operator()(block const &b, block_address index) const { return b.index_ > index; } }; class auto_block { public: auto_block() : b_(0) { } auto_block(block &b) : b_(&b) { } ~auto_block() { put(); } auto_block &operator =(block &b) { put(); b_ = &b; return *this; } void *get_data() const { if (b_) return b_->get_data(); throw std::runtime_error("auto_block not set"); } private: void put() { if (b_) { b_->put(); b_ = 0; } } block *b_; }; //-------------------------------- block_cache(file_utils::file_descriptor &fd, sector_t block_size, uint64_t max_nr_blocks, size_t mem); ~block_cache(); uint64_t get_nr_blocks() const; uint64_t get_nr_locked() const; enum get_flags { GF_ZERO = (1 << 0), GF_DIRTY = (1 << 1), GF_BARRIER = (1 << 2) }; block_cache::block &get(block_address index, unsigned flags, validator::ptr v); /* * Flush can fail if an earlier write failed. You do not know which block * failed. Make sure you build your recovery with this in mind. */ int flush(); void prefetch(block_address index); private: typedef bi::member_hook>, &block::list_hook_> list_hook_option; typedef bi::list> block_list; int init_free_list(unsigned count); block *__alloc_block(); void complete_io(block &b, int result); void issue_low_level(block &b, enum io_iocb_cmd opcode, const char *desc); void issue_read(block &b); void issue_write(block &b); void wait_io(); void unlink_block(block &b); void link_block(block &b); void relink(block &b); void wait_all(); void wait_specific(block &b); unsigned writeback(unsigned count); void setup_control_block(block &b); block *find_unused_clean_block(); block *new_block(block_address index); void mark_dirty(block &b); unsigned calc_nr_cache_blocks(size_t mem, sector_t block_size); unsigned calc_nr_buckets(unsigned nr_blocks); void zero_block(block &b); block *lookup_or_read_block(block_address index, unsigned flags, validator::ptr v); void exit_free_list(); void preemptive_writeback(); bool maybe_flush(block_cache::block &b); void release(block_cache::block &block); void check_index(block_address index) const; void hit(block &b, unsigned flags); void miss(unsigned flags); //-------------------------------- file_utils::file_descriptor &fd_; sector_t block_size_; uint64_t nr_data_blocks_; uint64_t nr_cache_blocks_; std::unique_ptr> blocks_memory_; unsigned char *blocks_data_; io_context_t aio_context_; std::vector events_; /* * Blocks on the free list are not initialised, apart from the * b.data field. */ block_list free_; block_list errored_; block_list dirty_; block_list clean_; // Because the block_list type doesn't have a constant time // size() method, we have to manually keep track of the list // sizes (tedious and error prone). unsigned nr_locked_; unsigned nr_dirty_; unsigned nr_io_pending_; block_list io_pending_; typedef bi::member_hook>, &block::set_hook_> block_option; typedef bi::set> block_set; block_set block_set_; // Stats unsigned read_hits_; unsigned read_misses_; unsigned write_zeroes_; unsigned write_hits_; unsigned write_misses_; unsigned prefetches_; validator::ptr noop_validator_; }; } //---------------------------------------------------------------- #endif