CMU 15-445 Lab1 - Buffer Pool Manager

Lab1 - Buffer Pool Manager

个人笔记

实验指导书

  1. 构建一个新的面向磁盘的存储管理器,这样的存储管理器假定数据库的主要存储位置在磁盘上。

  2. 在存储管理器中实现缓冲池。缓冲池负责将 pag主存磁盘来回移动。允许 DBMS 支持大于系统可用内存量的数据库。缓冲池的操作对系统中的其他部分是透明的。例如,系统使用其唯一标识符 ( page_id_t)向缓冲池请求页面,但它不知道该页面是否已经在内存中,或者系统是否必须从磁盘中检索它。

  3. 实现需要是线程安全的。多个线程将同时访问内部数据结构,因此需要确保临界区受到 latches的保护。

  4. 需要在存储管理器中实现以下两个任务:

    (1)LRU 替换原则

    (2)缓冲池管理器

  5. 任务 1 - LRU 替换策略

    (1)该组件负责跟踪缓冲池中的页面使用情况。将在 src/include/buffer/lru_replacer.h 中实现一个新的子类 LRUReplacer ,它相应的实现文件在 src/buffer/lru_replacer.cpp 。LRUReplacer 继承了抽象类 Replacer (src/include/buffer/replacer.h)。

    (2) LRUReplacer 的大小与 BufferPoolManager 相同,因为在 BufferPoolManager 中包含了所有 framesplaceholders 。 但是,并非所有 frames 都被视为 LRUReplacer. 将 LRUReplacer 被初始化为有没有 frames。然后,LRUReplacer 将只考虑新的没有划分的那些。

    (3)需要实现课程中讨论的 LRU 策略。您将需要实现以下方法:

    • Victim(T*) : Replacer 跟踪与所有元素相比最近访问次数最少的对象并删除,将其删除页号存储在输出参数中,并返回 True。如果 Replacer 为空则返回 False
    • Pin(T) :在将 page 固定到 BufferPoolManager 中的 frame 之后,应该调用此方法。它应该从 LRUReplacer 中删除固定包含固定 pageframe
    • Unpin(T):当页面的引用计数变为 0 时,应该调用此方法。这个方法应该将未包含固定 pageframe 添加到 LRUReplacer。(注意,需要判断是否超出了内存大小,如果超过了,则删除较新的页面,然后再添加。)
    • Size():这个方法返回当前在 LRUReplacer 中的页面数。
  6. 任务 2 - 缓冲池管理器

  • 接下来,需要在系统中实现缓冲池管理器 ( BufferPoolManager)。该 BufferPoolManager 负责从 DiskManager 中读取数据库页并将它们存储在内存中。BufferPoolManager 还可以在明确指示或需要为新页腾出空间时,将脏页写入磁盘。
  • 为了确保实现与系统的其余部分一起正常工作,提供了一些已经填写好的功能。也不需要实现实际读写数据到磁盘的代码(在给出的实现中称为 DiskManager)。
  • 系统中所有的内存页面都由 Page 对象表示。BufferPoolManager 并不需要了解这些页的内容。但是,作为系统开发人员,重要的是要理解 Page 对象只是缓冲池中用于存储内存的容器,因此不是特定于唯一的页面。也就是说,每个Page对象都包含一个内存块,DiskManager将它用作复制从磁盘读取的 page 内容的位置。当它来回移动到磁盘时,BufferPoolManager 将重用相同的 Page 对象来存储数据。这意味着相同的 Page 在系统的整个生命周期中可能包含不同的物理页面。Page 对象的标识符 (page_id) 跟踪它所包含的物理页面,如果 Page 对象不包含物理页,则必须将其 page_id 设置为INVALID_Page_id
  • 每个 Page 对象还维护了一个计数器,用于表示 “固定” 该页面的线程数。BufferPoolManager 不允许释放被 ”固定“ 的页面。每个 Page 对象还跟踪它标记的脏页。我们需要判断页面在解除绑定之前是否被修改过。BufferPoolManager 必须将 dirty Page 的内容写回磁盘,然后才能重用该对象。
  • BufferPoolManager 的实现将使用上述步骤中创建的 LRUReplacer 类。它将使用LRUReplacer 来跟踪 Page 对象被访问的时间,以便在必须释放 frame 来腾出空间给从磁盘复制的新物理页时决定删除哪个对象。
  • 需要实现头文件(src / compress / buffer_pool_pool_pool.h)中定义的在源文件 (src / buffer / buffer_pool_manager.cpp) 中的下列函数:
    • FetchPageImpl(page_id)
    • NewPageImpl(page_id)
    • UnpinPageImpl(page_id, is_dirty)
    • FlushPageImpl(page_id)
    • DeletePageImpl(page_id)
    • FlushAllPagesImpl()
  • 对于 FetchPageImpl,如果空闲列表中没有页面可用,并且所有其他页面当前都被固定,则应该返回 NULL。不管 FlushPageImpl 的引用状态如何,都应该刷新页面。
  1. 测试

    • LRUReplacer:

      test/buffer/lru_replacer_test.cpp

    • BufferPoolManager:

      test/buffer/buffer_pool_manager_test.cpp

    • 本地测试:

    // 别忘了删除测试中的 disabled
    
    $ mkdir build
    $ cd build
    $ make lru_replacer_test
    $ ./test/lru_replacer_test
    
    $ mkdir build
    $ cd build
    $ make buffer_pool_manager_test
    $ ./test/buffer_pool_manager_test
    
    // 检查
    $ cd build
    $ make format
    $ make check-lint
    $ make check-clang-tidy
    
  2. 提交:

    • src/include/buffer/lru_replacer.h

    • src/buffer/lru_replacer.cpp

    • src/include/buffer/buffer_pool_manager.h

    • src/buffer/buffer_pool_manager.cpp

    • 打包

      // 下面的写在一行,空格分隔。最好是写个bash脚本,比较方便。
      zip project1.zip 
      src/include/buffer/lru_replacer.h 
      src/buffer/lru_replacer.cpp 
      src/include/buffer/buffer_pool_manager.h 
      src/buffer/buffer_pool_manager.cpp
      

设计思路

  1. LRU(Least-Recently Used)的策略:
    • 数据一般是存储在磁盘上的,当我们需要读取一个数据的时候,会首先把它加载到内存中,然后返回给客户端。
    • 因为一般内存比磁盘小,容量有限,不可能同时存储那么多的数据。因此,内存会时常把暂时不需要的页面换回磁盘中,等到调用的时候再次置换回来。
    • LRU 正是页面置换算法的一种,最近最少使用的策略,它有一个潜在的假设,如果某个页的数据被访问过一次,那么下次再被访问的机率也就更高。
  2. LRU 的思路:
    • 首先,页面需要使用一个数据结构来存储,考虑到它需要不断的插入和删除,特别是需要头插和尾删,因此使用双向链表是比较方便的。另外,可以增加一个哈希表来加快查找和定位的速度(O(1))。综合考虑,使用双向链表 + Hash Table 来完成比较合适。
    • 双向链表负责维护一个页面的集合,数据按照从最近使用过的最近没有使用的来排序。因此,每当更新某个页面的时候,都应该把它放在双向链表的头部,即使是某个页面已经出现在链表中,也要拿出来,重新放置。每当置换的时候,都应该从双向链表的尾部置换页面,取出最近没有使用的页面。
    • Hash Table 负责维护一个从页面到链表节点的映射。它的作用有两个,一个是方便查找某个页面当前是否在这个链表当中。另一个是能够快速定位到当前页面在链表中的位置,方便删除操作。
  3. Clock 策略:
    • Clock 也是页面置换算法的一种。Clock 置换算法分为两种,一种是简单的置换算法,与 LRU 算法类似。另一种是改进型的,相比于前一种,减少了磁盘IO,性能更加高效。
    • 简单 Clock 的思想是先将内存中的所有页面想象成一个环形队列,通过维护一个访问位,每次更新的时候,如果访问位为 0,表示最近没有被访问,则可以置换;否则,将访问位置 0,继续寻找。
    • 改进版的思想是,需要添加一个修改位,修改了的置 1 ,没有则置 0 。那么当要置换时,(访问位,修改位)可能的组合按优先级分为以下四种:(0,0)、(1,0)、(0,1)、(1,1)。因此,它的执行过程是:(1)循环扫描查找(0,0)。有则退出。如果访问位为1则置 0 。(2)循环扫描查找(0,1)。有则退出。如果访问位为1则置 0 。(3)重复第一步。
  4. Clock 设计思路:
    • 这里只考虑简单的 Clock 设计思路。维护一个访问位数组和一个指针。每次当成环形数组循环查找当前需要被置换的页面。

Coding

C++ :std::scoped_lock 能够避免死锁的发生。它的构造函数能够自动进行上锁操作,析构函数会对互斥量进行解锁操作。能够保证线程安全。

  1. 根据上面 LRU 的设计思路,我们可以定义出 LRUReplacer 的数据结构,如下。

    class LRUReplacer : public Replacer {
     private:
      // TODO(student): implement me!
    	// 为了线程安全需要加的锁
      std::mutex latch;
    	// 这个表示 LRUReplacer 的大小,Buffer Pool大小相同。
      size_t capacity;
    	// 我们使用 双向链表 + 哈希表 的数据结构。
      std::list<frame_id_t> LRUList;
      // 使用 unordered_map(注意加头文件),从 frame_id 映射到 Node。 
      std::unordered_map<frame_id_t, std::list<frame_id_t>::iterator> LRUHash;
    };
    
  2. 根据上面 Clock 的设计思路,我们可以定义出 ClockReplacer 的数据结构,如下。

    
    class ClockReplacer : public Replacer {
     private:
      // TODO(student): implement me!
      // 对于每个 frame_id,都需要标记两个元素。isPin 表示当前 frame 是正在被引用。
      // ref 表示当前的 frame 最近是否被使用过。
      struct clockItem {
        bool isPin;
        bool ref;
      };
      // 为了线程安全需要加的锁
      std::mutex latch;
      // 这个表示 ClockReplacer 的大小,Buffer Pool大小相同。
      size_t victim_number;
      // clock 指针当前所指位置。
      size_t clockHand;
      // clockItem 数组,数组下表表示 frame_id。
      std::vector<clockItem> victimArray;
    };
    
  3. Replacer :追踪 page 使用情况的抽象类。类中不包含 page 的具体信息,只是为 Buffer Pool Manager 服务,提供了一个可以替换的 frame_id 。这个类包含了四个主要的函数。这里为了方便,将两个实现 LRUReplacer ClockReplacer 根据功能写在了一起,看的时候如果不舒服可以一次看一种实现方法。

    class Replacer {
     public:
      Replacer() = default;
      virtual ~Replacer() = default;
    
      /**
       * Remove the victim frame as defined by the replacement policy.
       * @param[out] frame_id id of frame that was removed, nullptr if no victim was found
       * @return true if a victim frame was found, false otherwise
       */
      virtual bool Victim(frame_id_t *frame_id) = 0;
    
      /**
       * Pins a frame, indicating that it should not be victimized until it is unpinned.
       * @param frame_id the id of the frame to pin
       */
      virtual void Pin(frame_id_t frame_id) = 0;
    
      /**
       * Unpins a frame, indicating that it can now be victimized.
       * @param frame_id the id of the frame to unpin
       */
      virtual void Unpin(frame_id_t frame_id) = 0;
    
      /** @return the number of elements in the replacer that can be victimized */
      virtual size_t Size() = 0;
    };
    

    (1)Victim 函数:

    • 用来移除可以被置换的 frame。 其中参数 frame_id 是【out】参数,也就是需要将 victimframe_id 写给调用者。而返回值是 bool 类型,如果成功删除了一个最近最少使用frame 则返回 true ,否则,返回 false

    • LRUReplacer 实现:

      bool LRUReplacer::Victim(frame_id_t *frame_id) {
        // 为了线程安全考虑,每个函数都需要加 mutex 锁。
        std::scoped_lock clock_lock{latch};
      
        // 首先根据 LRUHash 是否为空去判断是否有页面需要置换。
        if (LRUHash.empty()) {
          // 此时没有页面需要置换, 返回 false。
          return false;
        }
      
        // 先获取 frame_id。 更新LRUHash,删除映射关系。
        *frame_id = LRUList.back();
        LRUHash.erase(*frame_id);
        // 然后从 LRUList 的尾部删除一个最近最久未被访问过的 frame。
        LRUList.pop_back();
        return true;
      }
      
    • ClockReplacer 实现:

      bool ClockReplacer::Victim(frame_id_t *frame_id) {
        std::scoped_lock clock_lock{latch};
      
        // 如果当前页面没有全部被引用,还有可以被置换的页的时候。
        for (; Size() > 0; clockHand++) {
          // 循环查找可以被置换的页。
          if (clockHand == victimArray.size()) {
            clockHand = 0;
          }
      
          // 当前页面被引用,禁止置换。
          if (victimArray[clockHand].isPin) {
            continue;
          }
          // isPin 为 false, ref 为 true,此时要更新 ref。
          if (victimArray[clockHand].ref) {
            victimArray[clockHand].ref = false;
            continue;
          }
          // 我们需要的情况,isPin、ref 都为 false。
          victimArray[clockHand].isPin = true;
          // 返回要置换的 frame_id
          *frame_id = clockHand++;
          // 能够被置换的页面减少
          victim_number--;
          return true;
        }
        return false;
      }
      

    (2)Pin 函数:

    • Pin 一个页面,是指当调用了一个页面的时候,那么这个页面由于有了引用,则不能从缓冲区中移除,一直到当前页面的引用为 0 ,才可以。

    • LRUReplacer 实现:

      • 也就是说,我们的 LRUList 中维护的是可以被 victem 的页面,当 Pin 的时候,如果在 LRUList 中,则需要从中移除。
      void LRUReplacer::Pin(frame_id_t frame_id) {
        std::scoped_lock clock_lock{latch};
      
        // 如果当前页面被调用,则需要把它固定在 Buffer Pool 中。也就是说,需要把当前页面从待 victim 的 list 中移除。
        // 需要查看当前 frame 是否在 LRUHash 中。
        if (LRUHash.count(frame_id) != 0) {
          // 在 LRUHash 中,则移除。
          LRUList.erase(LRUHash[frame_id]);
          LRUHash.erase(frame_id);
        }
      }
      
    • ClockReplacer 实现:

      • ClockReplacercapacity 维护能够被置换的页面的总的数量。victimArray 维护所有页面的信息。
      void ClockReplacer::Pin(frame_id_t frame_id) {
        std::scoped_lock clock_lock{latch};
      
        // 如果当前页面没有被引用,则更新 isPin 为 true。
        // 代表能够被置换的页面减少。
        if (!victimArray[frame_id].isPin) {
          victimArray[frame_id].isPin = true;
          victim_number--;
        }
      }
      

    (3)Unpin 函数:

    • Unpin 一个页面,是指当一个页面的引用计数为0的时候,就把当前页面放入到待置换的数据结构中。

    • LRUReplacer 实现:

      void LRUReplacer::Unpin(frame_id_t frame_id) {
        std::scoped_lock clock_lock{latch};
      
        // 需要判断当前页面是否在待置换的 list 中。
        if (LRUHash.count(frame_id) == 0) {
          // 若不在,由于当前页面的引用为0,则把它加入到待置换的 list 头部中。
          LRUList.push_front(frame_id);
          LRUHash[frame_id] = LRUList.begin();
        }
      }
      
    • ClockReplacer 实现:

      void ClockReplacer::Unpin(frame_id_t frame_id) {
        std::scoped_lock clock_lock{latch};
      
        // 如果当前页面之前被引用过,当引用计数为 0 时,需要更新 isPin 为 false。
        // 因为被引用过,因此 ref 此时需要更新为 true。
        // 能够被置换的页面增加。
        if (victimArray[frame_id].isPin) {
          victimArray[frame_id].isPin = false;
          victimArray[frame_id].ref = true;
          victim_number++;
        }
      }
      

    (4)Size 函数:

    • 返回一个当前待置换的 fame 数量。

    • LRUReplacer 实现:

      size_t LRUReplacer::Size() { return LRUList.size(); }
      
    • ClockReplacer 实现:

      size_t ClockReplacer::Size() { return victim_number; }
      

    (5)构造函数:

    • 需要补充完整构造函数。

    • LRUReplacer 实现:

      LRUReplacer::LRUReplacer(size_t num_pages) { capacity = num_pages; }
      
    • ClockReplacer 实现:

      ClockReplacer::ClockReplacer(size_t num_pages) {
        victim_number = 0;
        clockHand = 0;
        // 初始化 victimArray。isPin 最开始为 true 是因为我们把页面加载到 Buffer pool 的时候,一定是因为 page 被引用了。
        // ref 为 false,因为当前这个 page 的引用还没有结束。
        for (size_t i = 0; i < num_pages; i++) {
          victimArray.emplace_back(clockItem{true, false});
        }
      }
      
  4. Buffer Pool Manager :对缓冲池进行管理。其主要的数据结构是一个 page 数组,下标表示 frame_id 。还有一个哈希表,表示从 page_idframe_id 的映射。

    • FindPage 把查找一个 frame 的操作单独拿出来,方便调用。

      bool BufferPoolManager::FindPage(frame_id_t *replaceFrameId) {
        // 1. 查看 free_list_,如果有空闲,则 Buffer Pool 没有满,从前面拿一个 frameId 返回。
        if (!free_list_.empty()) {
          *replaceFrameId = free_list_.front();
          free_list_.pop_front();
          return true;
        }
      
        // 2. Buffer Pool 满了,则寻找是否有可以被替换的页,没有则返回 false。
        if (!replacer_->Victim(replaceFrameId)) {
          return false;
        }
      
        // 3. 获得当前 replaceFrameId 对应的 page。
        Page *page = &pages_[*replaceFrameId];
        if (page->is_dirty_) {
          // 4. 刷新到磁盘。
          disk_manager_->WritePage(page->page_id_, page->data_);
        }
        page_table_.erase(page->page_id_);
      
        return true;
      }
      
    • FetchPageImpl

      Page *BufferPoolManager::FetchPageImpl(page_id_t page_id) {
        // 1.     Search the page table for the requested page (P).
        // 1.1    If P exists, pin it and return it immediately.
        // 1.2    If P does not exist, find a replacement page (R) from either the free list or the replacer.
        //        Note that pages are always found from the free list first.
        // 2.     If R is dirty, write it back to the disk.
        // 3.     Delete R from the page table and insert P.
        // 4.     Update P's metadata, read in the page content from disk, and then return a pointer to P.
        std::scoped_lock lock{latch_};
      
        // 1. 从 table 中寻找请求的页
        std::unordered_map<page_id_t, frame_id_t>::iterator iter = page_table_.find(page_id);
        // 1.1 请求的页存在,pin,并返回。
        if (iter != page_table_.end()) {
          // 找到了,pin,通知 Replacer
          replacer_->Pin(iter->second);
          pages_[iter->second].pin_count_++;
          return &pages_[iter->second];
        }
      
        // 1.2 请求的页不存在,从 free_list 或 replacer 中找到一个替换页(R)。
        frame_id_t replaceFrameId = INVALID_PAGE_ID;
        // 2|3. 没有找到替换的页,返回。
        if (!FindPage(&replaceFrameId)) {
          return nullptr;
        }
      
        // 4. Update P 的元数据。
        Page *newPage = &pages_[replaceFrameId];
        page_table_[page_id] = replaceFrameId;
        newPage->page_id_ = page_id;
        newPage->pin_count_ = 1;
        newPage->is_dirty_ = false;
        disk_manager_->ReadPage(page_id, newPage->data_);
        // 通知 replacer。
        replacer_->Pin(replaceFrameId);
      
        return newPage;
      }
      
    • UnpinPageImpl

      bool BufferPoolManager::UnpinPageImpl(page_id_t page_id, bool is_dirty) {
        std::scoped_lock lock{latch_};
      
        // 1. 查看该页是否在 Buffer Pool 中。
        std::unordered_map<page_id_t, frame_id_t>::iterator iter = page_table_.find(page_id);
        if (iter == page_table_.end()) {
          return false;
        }
      
        // 2. 在 Buffer Pool 中,处理 count。
        frame_id_t frameId = iter->second;
        Page *page = &pages_[frameId];
        // 2.1 已经没有引用了,直接返回。
        if (page->pin_count_ <= 0) {
          return false;
        }
        // 需要先判断是否需要减。
        page->pin_count_--;
        // 2.2 更新 is_dirty_
        if (is_dirty) {
          page->is_dirty_ = true;
        }
        // 3. 通知 Replacer。
        if (page->pin_count_ == 0) {
          replacer_->Unpin(frameId);
        }
        return true;
      }
      
    • FlushPageImpl

      bool BufferPoolManager::FlushPageImpl(page_id_t page_id) {
        // Make sure you call DiskManager::WritePage!
        std::scoped_lock lock{latch_};
      
        // 1. 查看该页是否在 Buffer Pool 中, 是否无效。
        std::unordered_map<page_id_t, frame_id_t>::iterator iter = page_table_.find(page_id);
        if (iter == page_table_.end() || page_id == INVALID_PAGE_ID) {
          return false;
        }
      
        // 2. 刷新到磁盘。
        Page *page = &pages_[iter->second];
        // 不管引用状态如何,都要刷新到磁盘。
        disk_manager_->WritePage(page_id, page->data_);
        page->is_dirty_ = false;
        return true;
      }
      
    • NewPageImpl

      Page *BufferPoolManager::NewPageImpl(page_id_t *page_id) {
        // 0.   Make sure you call DiskManager::AllocatePage!
        // 1.   If all the pages in the buffer pool are pinned, return nullptr.
        // 2.   Pick a victim page P from either the free list or the replacer. Always pick from the free list first.
        // 3.   Update P's metadata, zero out memory and add P to the page table.
        // 4.   Set the page ID output parameter. Return a pointer to P.
        std::scoped_lock lock{latch_};
      
        // 1|2. 挑选一个 victim page
        frame_id_t victimFrameId = -1;
        if (!FindPage(&victimFrameId)) {
          return nullptr;
        }
      
        // 0. 分配一个新的页号。
        *page_id = disk_manager_->AllocatePage();
      
        // 3. 更新 page 的元数据。
        Page *newPage = &pages_[victimFrameId];
        newPage->page_id_ = *page_id;
        newPage->is_dirty_ = false;
        newPage->pin_count_ = 1;
        // 添加到 page table。
        page_table_[*page_id] = victimFrameId;
        replacer_->Pin(victimFrameId);
        // 创建的新页需要写回磁盘。
        disk_manager_->WritePage(*page_id, newPage->data_);
        return newPage;
      }
      
    • deletePageImpl

      bool BufferPoolManager::DeletePageImpl(page_id_t page_id) {
        // 0.   Make sure you call DiskManager::DeallocatePage!
        // 1.   Search the page table for the requested page (P).
        // 1.   If P does not exist, return true.
        // 2.   If P exists, but has a non-zero pin-count, return false. Someone is using the page.
        // 3.   Otherwise, P can be deleted. Remove P from the page table, reset its metadata and return it to the free list.
        std::scoped_lock lock{latch_};
      
        // 1. 查看页表中 page 是否存在。
        std::unordered_map<page_id_t, frame_id_t>::iterator iter = page_table_.find(page_id);
        if (iter == page_table_.end()) {
          return true;
        }
      
        // 2. 查看是否有非 0 的引用
        Page *deletePage = &pages_[iter->second];
        if (deletePage->pin_count_ > 0) {
          return false;
        }
      
        // 3. 删除 P
        if (deletePage->is_dirty_) {
          disk_manager_->WritePage(deletePage->page_id_, deletePage->data_);
        }
        // 调用删除
        disk_manager_->DeallocatePage(page_id);
        page_table_.erase(page_id);
        // 重置页面元数据
        deletePage->page_id_ = INVALID_PAGE_ID;
        deletePage->pin_count_ = 0;
        deletePage->is_dirty_ = false;
        // 更新 free_list.
        free_list_.push_back(iter->second);
        return true;
      }
      
    • FlushAllPagesImpl

      void BufferPoolManager::FlushAllPagesImpl() {
        // You can do it!
        std::scoped_lock lock{latch_};
      
        for (size_t i = 0; i < pool_size_; i++) {
          disk_manager_->WritePage(pages_[i].page_id_, pages_[i].data_);
        }
      }
      
  5. DiskManager 主要是读写磁盘操作。

    • ReadPage 从数据库文件中读取一个 page 。读取指定 page_id 的数据到 page_datapage_data 是一个【out】输出参数。

      /**
      	 * Read the contents of the specified page into the given memory area.
         * Read a page from the database file.
         * @param page_id id of the page
         * @param[out] page_data output buffer
      */
      void DiskManager::ReadPage(page_id_t page_id, char *page_data) {
        // 获取偏移位置。
        int offset = page_id * PAGE_SIZE;
        // check if read beyond file length
        if (offset > GetFileSize(file_name_)) {
          //....
        } else {
          // set read cursor to offset
          db_io_.seekp(offset);
          // 读取数据到 page_data。
          db_io_.read(page_data, PAGE_SIZE);
      		//....
          // if file ends before reading PAGE_SIZE
          int read_count = db_io_.gcount();
          if (read_count < PAGE_SIZE) {
      			//....
            // 数据不够则用 0 补齐。
            memset(page_data + read_count, 0, PAGE_SIZE - read_count);
          }
        }
      }
      
    • WritePage 将数据写入到磁盘中。写入指定 page_idpage_data

      /**
         * Write the contents of the specified page into disk file.
      	 * Flush the entire log buffer into disk.
         * @param log_data raw log data
         * @param size size of log entry
      */
      void DiskManager::WritePage(page_id_t page_id, const char *page_data) {
        // 获取偏移。
        size_t offset = static_cast<size_t>(page_id) * PAGE_SIZE;
        // set write cursor to offset
        num_writes_ += 1;
        db_io_.seekp(offset);
        // 将 page_data 写入.
        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();
      }
      

你可能感兴趣的:(CMU,15-445,学习笔记,c1)