见 CMU 15-445 Project #0 - C++ Primer 中的准备工作。
首先要区分缓冲池中 Page
与 Frame
,这个其实和操作系统分页管理中页面和页框的关系是类似的。页框是内存中一个个实际的空间,但它只是空间,没有内容。而页面则是被放置在页框中的实际内容,同时每一个内存中的页面都有它的外存映像,他们共用同一个 page_id
,并通过脏位标识数据回写。
此外,还有几个问题在实现时要注意:
FetchPgImp
中不管页面是已经存在与缓冲池,还是通过LRU-K置换得到,都需要UnpinPgImp
中的脏位修改仅限于 false -> true
的单向修改,因为一个页面脏位 true -> false
的修改必须与外存回写 WritePage
配合。DeletePgImp
中在进行删除前也要通过脏位判断是否需要回写。NewPgImp
auto NewPgImp(page_id_t *page_id) -> Page * override {
std::scoped_lock<std::mutex> locker(latch_);
frame_id_t frame_id = 0; // 用于记录分配给新页面的页框号
/* 如果空闲链表不为空,直接从空闲链表中获取一个空的页框来初始化页面;
* 否则需要通过LRU-K淘汰现有page,然后在对应frame中新建page。 */
if (!free_list_.empty()) {
/* 从空闲链表中获取frame_id */
frame_id = free_list_.front();
free_list_.pop_front();
} else {
/* 通过LRU-K获取应该被淘汰的页框 */
if (!replacer_->Evict(&frame_id)) {
*page_id = INVALID_PAGE_ID;
return nullptr;
}
/* 如果脏位为true需要进行回写 */
if (pages_[frame_id].IsDirty()) {
disk_manager_->WritePage(pages_[frame_id].GetPageId(), pages_[frame_id].GetData());
}
/* 从page_table_删除页面和页框之间的映射 */
page_table_->Remove(pages_[frame_id].GetPageId());
}
/* 获取页面编号 */
*page_id = AllocatePage();
/* 初始化页面元数据 */
pages_[frame_id].page_id_ = *page_id;
pages_[frame_id].pin_count_++;
pages_[frame_id].is_dirty_ = false;
/* 在可扩展哈希表中建立page_id与frame_id的编号 */
page_table_->DoInsert(*page_id, frame_id);
/* 记录指定页框的访问并将该页框设置为不可驱逐 */
replacer_->RecordAccess(frame_id);
replacer_->SetEvictable(frame_id, false);
return pages_ + frame_id;
}
FetchPgImp
auto FetchPgImp(page_id_t page_id) -> Page * override {
std::scoped_lock<std::mutex> locker(latch_);
int frame_id; // 待操作的页框号
/* 如果待查找页面位于缓冲池,直接返回即可 */
if (page_table_->Find(page_id, frame_id)) {
/* 增加页面访问次数 */
pages_[frame_id].pin_count_++;
/* 记录指定页框的访问并将该页框设置为不可驱逐 */
replacer_->RecordAccess(frame_id);
replacer_->SetEvictable(frame_id, false);
return pages_ + frame_id;
}
/* 如果空闲链表不为空,直接从空闲链表中获取一个空的页框来初始化页面;
* 否则需要通过LRU-K淘汰现有page,然后在对应frame中新建page。 */
if (!free_list_.empty()) {
/* 从空闲链表中获取frame_id */
frame_id = free_list_.front();
free_list_.pop_front();
} else {
/* 通过LRU-K获取应该被淘汰的页框 */
if (!replacer_->Evict(&frame_id)) {
return nullptr;
}
/* 如果脏位为true需要进行回写 */
if (pages_[frame_id].IsDirty()) {
disk_manager_->WritePage(pages_[frame_id].GetPageId(), pages_[frame_id].GetData());
}
/* 从page_table_删除页面和页框之间的映射 */
page_table_->Remove(pages_[frame_id].GetPageId());
}
/* 初始化页面元数据 */
pages_[frame_id].page_id_ = page_id;
pages_[frame_id].is_dirty_ = false;
pages_[frame_id].pin_count_++;
/* 写入页面的数据 */
disk_manager_->ReadPage(page_id, pages_[frame_id].GetData());
/* 在可扩展哈希表中建立page_id与frame_id的编号 */
page_table_->DoInsert(page_id, frame_id);
/* 记录指定页框的访问并将该页框设置为不可驱逐 */
replacer_->RecordAccess(frame_id);
replacer_->SetEvictable(frame_id, false);
return pages_ + frame_id;
}
UnpinPgImp
auto UnpinPgImp(page_id_t page_id, bool is_dirty) -> bool override {
std::scoped_lock<std::mutex> locker(latch_);
int frame_id; // 待操作的页框号
/* 查询并修改缓冲池中指定页面的信息 */
if (page_table_->Find(page_id, frame_id)) {
/* 如果当前页面pin_count_已经为0直接返回false */
if (pages_[frame_id].GetPinCount() == 0) {
return false;
}
/* 这里的脏位变换只支持从false到true,从true到false需要与回写配合 */
if (!pages_[frame_id].IsDirty() && is_dirty) {
pages_[frame_id].is_dirty_ = true;
}
/* 如果当前操作使得pin_count_值为0,需要将对应页面设置为可驱逐。 */
if (--pages_[frame_id].pin_count_ == 0) {
replacer_->SetEvictable(frame_id, true);
}
return true;
}
return false;
}
FlushPgImp
auto FlushPgImp(page_id_t page_id) -> bool override {
std::scoped_lock<std::mutex> locker(latch_);
int frame_id; // 待操作的页框号
/* 如果指定页面存在且脏位为true需要进行回写 */
if (page_table_->Find(page_id, frame_id)) {
if (pages_[frame_id].IsDirty()) {
disk_manager_->WritePage(pages_[frame_id].GetPageId(), pages_[frame_id].GetData());
pages_[frame_id].is_dirty_ = false;
}
return true;
}
return false;
}
FlushAllPgsImp
void FlushAllPgsImp() override {
std::scoped_lock<std::mutex> locker(latch_);
/* 依次回写所有的脏页 */
for (int frame_id = 0; frame_id < static_cast<int>(pool_size_); frame_id++) {
if (pages_[frame_id].GetPageId() != INVALID_PAGE_ID && pages_[frame_id].IsDirty()) {
disk_manager_->WritePage(pages_[frame_id].GetPageId(), pages_[frame_id].GetData());
pages_[frame_id].is_dirty_ = false;
}
}
}
DeletePgImp
auto DeletePgImp(page_id_t page_id) -> bool override {
std::scoped_lock<std::mutex> locker(latch_);
int frame_id; // 待操作的页框号
if (page_table_->Find(page_id, frame_id)) {
/* 无法删除被固定的页面 */
if (pages_[frame_id].GetPinCount() > 0) {
return false;
}
/* 如果脏位为true需要进行回写 */
if (pages_[frame_id].IsDirty()) {
disk_manager_->WritePage(pages_[frame_id].GetPageId(), pages_[frame_id].GetData());
}
/* 从page_table_和replacer_中删除页面的相关信息 */
page_table_->Remove(page_id);
replacer_->Remove(frame_id);
/* 重置页面的在内存中的缓存和元数据 */
pages_[frame_id].ResetMemory();
pages_[frame_id].page_id_ = INVALID_PAGE_ID;
pages_[frame_id].is_dirty_ = false;
pages_[frame_id].pin_count_ = 0;
/* 将页框归还给空闲链表 */
free_list_.emplace_back(frame_id);
/* 重置页面在外存中的数据 */
DeallocatePage(page_id);
return true;
}
return true;
}
提交后的结果如下,所有测试用例全部通过,由于没有对并发性能进行优化,所以执行效率一般。