cmu15445_lab1_BUFFER POOL

Buffer Pool Manager

整理一下cmu15445的实验的实现内容,具体实验的代码写的太丑就不公开了。

Page

pagepage是一个数据库中存储的最小单位,也是磁盘和内存交换的最小单位,每一个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是才能放进LRU
  • is_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在内存中的位置,同时读取和写入,我们通过ifstreamostream的方式向磁盘中读取和写入数据。

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)Unpin BufferPool中的一个Page,减少其pin_count,如果pin_count == 0就将其从LRU中移除,如果is_dirtytrue,就要修改对应的pageis_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_dirtyfalse.
  • FlushAllPagesImpl() 刷新所有的page

PARALLEL BUFFER POOL MANAGER

一个并发的缓冲池,通过简单的取模运算来将对应的page_id映射到对应的缓冲池中,直接调用之前实现好的缓冲池的接口即可。

posted on 2022-11-29 23:05  翔鸽  阅读(147)  评论(0编辑  收藏  举报