cmu15445_lab1_BUFFER POOL
Buffer Pool Manager
整理一下cmu15445的实验的实现内容,具体实验的代码写的太丑就不公开了。
Page
page:page是一个数据库中存储的最小单位,也是磁盘和内存交换的最小单位,每一个page都有一个page_id, 在storage/page/page.h中有该文件的实现,Page类中主要维护了以下几个信息:
inline void ResetMemory() { memset(data_, OFFSET_PAGE_START, PAGE_SIZE); }
char data_[PAGE_SIZE]{};
page_id_t page_id_ = INVALID_PAGE_ID;
int pin_count_ = 0;
bool is_dirty_ = false;
ReaderWriterLatch rwlatch_;
data用来表示一个Page存储的基本信息page_id是为每一个page分配的唯一表示pin_count用来表示当前有多少个线程在占用该类, 当有线程在占用该page的时候就不能将其放在LRU中,只有当pint_count == 0是才能放进LRUis_dirty用来表示当前page是否被修改过,内存中的page和磁盘中的page的内容是否一致,如果不一致要刷盘。
有一个地方要注意区分的是page_id是一个page在磁盘中的唯一表示,frame_id是一个page对应内存中的编号,frame(内存帧),是一个page在内存中的表示。
LRU REPLACEMENT POLICY
内存的大小比磁盘要小很多,我们需要逐步替换掉内存中的frame来换进磁盘中的page,采用LRU最近最就未使用算法来实现。
std::unordered_map<frame_id_t, std::list<frame_id_t>::iterator> to_pos_;
std::list<frame_id_t> lru_cache_;
采用双向链表和hash_table来实现,我们将刚刚使用过的frame都放在链表的头部,很久没使用的放在链表的尾部。
Victim(frame_id_t*), 淘汰掉一个最久未使用的frame,直接移除链表的最后一个节点即可。Pin(frame_id_t), 表示有一个进程需要使用BufferPoolManager中的一个frame,就Pin一下将这个frame移除LRU.void Unpin(frame_id_t), 如果一个帧没有线程使用就存入LRU等待被淘汰。Size()返回一个LRU的大小。
BUFFER POOL MANAGER INSTANCE
DISK MANAGER
DiskManager用来管理一个数据库中pages的分配和释放,以及用来读和写一个page的内容到磁盘中,同时维护了一个日志文件的读写和分配。
应为每一个page的大小是固定的我们通过page_id * page_size 来找到该page在内存中的位置,同时读取和写入,我们通过ifstream和ostream的方式向磁盘中读取和写入数据。
void DiskManager::WritePage(page_id_t page_id, const char *page_data) {
std::scoped_lock scoped_db_io_latch(db_io_latch_);
size_t offset = static_cast<size_t>(page_id) * PAGE_SIZE;
// set write cursor to offset
num_writes_ += 1;
db_io_.seekp(offset);
db_io_.write(page_data, PAGE_SIZE);
// check for I/O error
if (db_io_.bad()) {
LOG_DEBUG("I/O error while writing");
return;
}
// needs to flush to keep disk file in sync
db_io_.flush();
}
/**
* Read the contents of the specified page into the given memory area
*/
void DiskManager::ReadPage(page_id_t page_id, char *page_data) {
std::scoped_lock scoped_db_io_latch(db_io_latch_);
int offset = page_id * PAGE_SIZE;
// check if read beyond file length
if (offset > GetFileSize(file_name_)) {
LOG_DEBUG("I/O error reading past end of file");
// std::cerr << "I/O error while reading" << std::endl;
} else {
// set read cursor to offset
db_io_.seekp(offset);
db_io_.read(page_data, PAGE_SIZE);
if (db_io_.bad()) {
LOG_DEBUG("I/O error while reading");
return;
}
// if file ends before reading PAGE_SIZE
int read_count = db_io_.gcount();
if (read_count < PAGE_SIZE) {
LOG_DEBUG("Read less than a page");
db_io_.clear();
// std::cerr << "Read less than a page" << std::endl;
memset(page_data + read_count, 0, PAGE_SIZE - read_count);
}
}
}
BUFFER POOL MANAGER INSTANCE
BufferPoolManagerInstance实在LRU的基础上来管理我们的内存中的page, 主要维护了一下几个信息:
const size_t pool_size_; //BuufferPool 的大小
const uint32_t num_instances_ = 1; //用来维护并行bufferpool的数量
const uint32_t instance_index_ = 0; //在bufferpool池中的编号
std::atomic<page_id_t> next_page_id_{instance_index_}; //每一个bufferpool都用来维护一个bufferpool的pageid
Page *pages_; //一个bufferpool中的pages数组,每一个帧对应的Pages
/** Pointer to the disk manager. */
DiskManager *disk_manager_ __attribute__((__unused__));
/** Pointer to the log manager. */
LogManager *log_manager_ __attribute__((__unused__));
std::unordered_map<page_id_t, frame_id_t> page_table_; //page_table用来维护page_id和frame_id之间的映射关系
Replacer *replacer_; //替换规则
std::list<frame_id_t> free_list_; //空闲的帧
std::mutex latch_; //latch锁来保护bufferpool内部的数据结构
FetchPgImp(page_id)用来获取一个page, 首先我们先判断page_table中是否以及有对应的page_id如果有的话直接从中提取,同时要记得Pin一下,同时增加其引用计数。如果page_table没有对应的page_id就去空闲链表中查看,如果空闲链表中有剩余的frame,就提取出来如果没有就从LRU中采用淘汰算法淘汰一个,然后找到对应的pages, 注意这个时候我们需要判断淘汰的frame是否是脏页,如果是脏页就需要刷盘,同时移除page_table旧的frame对应的page_id更新为新的page_id, 并将其pin_count设置为1并Pin一下。UnpinPgImp(page_id, is_dirty),UnpinBufferPool中的一个Page,减少其pin_count,如果pin_count == 0就将其从LRU中移除,如果is_dirty为true,就要修改对应的page的is_dirty。FlushPgImp(page_id)刷盘,更新对应page的磁盘内容, 刷盘之后注意要将is_dirty设置为false, 因为此时磁盘中和内存中的内容相同了。NewPgImp(page_id)在buffer_pool中新分配一个page, 和FetchPgImp相似,我们现在空闲链表中查找空闲的frame,如果空闲链表为空就执行LRU淘汰一个没有进程使用的frame,如果淘汰的frame为脏页注意要刷盘,然后调用AllocatePage分配一个页,更新Page_tabl的映射关系。注意这个时候需要刷盘,因为这里只是新分配一个页,磁盘里面是没有这个page的,需要刷盘将其写入磁盘,也要Pin一下。DeletePgImp(page_id)从BufferPool中删除该页,删除完之后注意将对应的frame增加到空闲链表当中,同时设置is_dirty为false.FlushAllPagesImpl()刷新所有的page。
PARALLEL BUFFER POOL MANAGER
一个并发的缓冲池,通过简单的取模运算来将对应的page_id映射到对应的缓冲池中,直接调用之前实现好的缓冲池的接口即可。
浙公网安备 33010602011771号