📄 ҳ
字号:
关于页管理器
提供外存文件页缓存管理 --- 目的:减少对外存的访问次数
包含:页缓存最大数量管理,页缓存的分配与释放,页缓存满时淘汰换页机制
页文件头信量要求:页的大小,
设计要求:
提供页号与页缓存的映射管理(哈希表实现)
页缓存满时,如果不存在空闲页缓存,页淘汰算法,(采用先进先出机制),
提供空闲页缓链表,由于换页需要将内容中的数据写入页文件.
尽量使用已经备份至日志文件的空闲页缓存.如果不存在,需要先同步备份日志,然后执行换页
提供备份日志机制,用于更改回滚,以及断电数据完整性保证 --- 目的:更改可以丢失,但原有的数据完整性不被破坏
包含:断电保护回滚,上层应用的commit rollback机制支持.
设计要求:
当用户往页缓存执行写入时(用户写页之前,必须将该页读入缓存)
只往备份日志文件里写,并对每页计算校验和(写入该未尾之后的4个字节)
用户提交更改时,先把备份日志文件同步至外存
然后才将缓存中的脏页(提供脏页链表记录)写入页文件.之后删除备份日志文件
程序重启时,初始化如果发现存在备份日志文件,则应根据日志文件里的信息,
依次还原页文件,以便维持数据的完整性.
每个页缓存需要记录自己是否已经被写入日志文件了,如果是,则当执行写入时,
不再往备份日志里写.相关的信息页缓存自己记录,同时由于页缓存会被换出内存,
需要页管理器做一个所有页缓存是否进入备份日志的位着色表.
当一个完整的页缓存提交被执行,所有的页缓存的标识位与以备份着色表都将被清0
(提交完整,意味着数据是完整的,自然可以取消相应的备份标识)
回滚:当上层使用回滚,则根据具体的情况,或从原始文件中重新回载页缓存
或者(由于缓存满交换)从备份日志里回滚.
页缓存的重载,与释放均需要通过回调函数通知上层.
提供外存文件空闲碎片整理 --- 目的:防止页文件变得过大,即时回收不使用的页,但会有额外的运行时间消耗,根据实际需要打开或关闭(或设成以一定的百分之自动整理)
包含:提供开关机制(碎片整理可以打开或关闭),提供空闲碎片整理接口(强行整理),以及自动整理接口(用户设置使用率)
页文件头信量:第一空闲页索引记录,是否打开空闲碎片整理机制,空闲页总数记录
设计要求:
如果开关由关闭置成打开,则需要对现有的空闲页面按页号进行排序.
当释放一页,如果碎片整理机制未被打开,则将该加入空闲"链表"即可,即一个空闲页(主空闲页),管理着若干页空闲页(数量由页的大小决定)
如果主空闲页满了,则将新的页置该之前,成为新的主空闲页,并链接原来的主空闲页.
如果碎片整理机制打开,则需要对空闲页进行页号排序.保证页号小的空闲页排在前面,保证空闲页整理的效率.当释放一页时,沿着"链表"查找,要合适的页添加
如果页已满,应按页号跨度最大的原则分裂该页.
每释放一页,空闲页计数应增加一,并写回外存页文件.
当设置了自动碎片整理时,用于管理的空闲页的页号应是它所记载的空闲页中最大的(在分配置时可以减少写入,会导致释放时多读一次"链表").
当请求一页时,从空闲页"链表"里取出第一个空闲页即可.
注:
空闲页 != 空闲页缓存
空闲页表示外存页文件中不被使用的区域
空闲页缓存则可能是有意义的页,只是它的引用计数已经为零,可以在缓存满时被回收,相当于页缓存内存池机制
函数设计
/**
* @brief 创建一个缓存页管理器
* @param hm:内存池句柄
* @param file_name:辅存的文件名
* @param page_release_cb:页析构回调函数
* @param page_reload_cb:页重载回调函数
* @param extra_size:每页附加的扩展用户数据大小
* @param max_page:缓存中的最大页数
* @param page_size:每一页的大小,如果对应的页文件已存在,则返回页的大小,可为空,默认为1024
* @param used_rate:外存文件使用率, 0:表示不需要进行栽减, 1-9分别表示使用率,一旦低于使用率,则进行自动栽减,>=10表示总是要栽减
*/
extern HPAGER PagerConstructor(HMYMEMPOOL hm,
const char * file_name,
PAGE_RELEASE page_release_cb,
PAGE_RELOAD page_reload_cb,
PAGE_MOVE page_move_cb,
size_t extra_size,
size_t max_cache_pages,
size_t * page_size,
int used_rate);
extra_size:一个对于上层比较重要的参数,表示用户对每页缓存附加的数据缓冲的大小
page_release_cb:在页缓存被释放时,将通过此函数回调通知上层.
page_reload_cb:在页缓存被重新加载时,将通过此函数回调通知上层.
page_move_cb:当页被移动时(栽减页文件时会出现),通过此回调函数通上层.
在创建页管理器时,需要判断页文件是否已经存在,如果已存在则需要将页文件在相关信息读出,并修正用户传入的参数page_size,
用户在根据page_size的值来对页缓冲进行读写操作,避免内存越界.
判断用户传进来的used_rate参数,如果非0,则做一些栽减判断,并根据需要进行栽减
/**
* @brief 销毁一个缓存页管理器
*/
extern void PagerDestructor(HPAGER hpgr);
/**
* @brief 同步所有脏缓存页
*/
extern int PagerSyn(HPAGER hpgr);
首先将jfd文件同步至外存,将所有页缓存至成同步过jfd
将同步过的空闲页缓存加入指定的链表(用于页缓存满时,换页优先从这个链表里取空闲页缓存)
将所有的dirty页写入fd中,将dirty链表置空.每页的dirty标识也置空
删除份的jfd文件.
jfd着色表以及页的injournal标识均置成0,因为一个完整提交已经完成了.
/**
* @brief 取消所有的页的更改
*/
extern int PagerRollBack(HPAGER hpgr);
在PagerSyn之前,RollBack是有效的,PagerSyn之后,RollBack没有意义了.
RollBack判断页文件是否已经同步过jfd,并且向fd里写了数据.此时应从jfd里回滚所有页
如果没有,则重新加载fd里相应的页至脏页缓存即可,
删除份的jfd文件.
jfd着色表以及页的injournal标识均置成0,因为数据恢复成完整状态.
/**
* @brief 申请一页
*/
extern size_t PagerGetPageNo(HPAGER hpgr);
首先从页文件的第一页寻找第一个空闲页的页号.
如果空闲页存在,则取出第一个空闲页(如果用于管理的空闲页没有任子页,则更改第一个空闲页的索引至下一个空闲页),
相应的页应要做写入操作(备份至jfd,等提交时真正写入),第一页(有可能要写),第一个主空闲页(一定要写)
这些页需要被加载进缓存
如果不存在任何空闲页,意味着页文件需要增长了,计算页文件总共有多少页,其值加1则为新的页号
找到页号的缓存与页缓存进入哈希表(如果不存在),并将缓存的内容置成0
将此页的第一个字节置成0xff表示此页已被使用.
/**
* @brief 释放一个页号,将置成空闲页
*/
extern int PagerReleasePageNo(HPAGER hpgr, size_t pgno);
将页的第一个字节置成0,表示此页不再使用了
判断是否打开的自去除空闲页的机制
如果没有,则将空闲页号填入主空闲页即可,如果主空闲页满了,则扩展空闲页"链表",改变主空闲页链表
主空闲需要写入(如果未满), 如果主空闲页满了:第一页需要写入,空闲页->主空闲页(即空闲页本身需要写入)
如果有,
则需要对空闲页进行页号排序.保证页号小的空闲页排在前面,保证空闲页整理的效率.当释放一页时,沿着"链表"查找,要合适的页添加
如果页已满,应按页号跨度最大的原则分裂该页.
每释放一页,空闲页计数应增加一,并写回外存页文件.
当设置了自动碎片整理时,用于管理的空闲页的页号应是它所记载的空闲页中最大的(在分配置时可以减少写入,会导致释放时多读一次"链表").
未满:页号添加至的那个空闲页, 如已满:还需写分裂后新的承载页
第一页需要被写入(需要更改空闲页数)
如果设置了自动truncate,此时判断是否达到了百分比,如果达到了,truncate开始执行.
/**
* @brief 获取当前页文件中有多少页
*/
extern size_t PagerGetTotalPagesCount(HPAGER hpgr);
/**
* @brief 获取当前页文件中有多少空闲页
*/
extern size_t PagerGetFreePages(HPAGER hpgr);
/**
* @brief 去掉page文件中的空闲页
*/
extern int PagerTruncate(HPAGER hpgr);
先对空闲页"链表"进行排序,然后从最后一页开始检查,如果该页不是空闲页,则将它移动到第一个空闲页,
如果是空闲页,则跳过.直到所有的空闲页都填满了,调用栽减文件函数改小页文件尺寸
/**
* @brief 重设使用率
*/
extern int PagerSetUsedRate(HPAGER hpgr, int used_rate);
重设使用率,如果used_rate由零变成零,需要做一次排序
/**
* @brief 从页缓存管理中获取一页 与PagerReleasePage是对偶的操作
*/
extern HPAGE_HEAD PagerGetPage(HPAGER hpgr, size_t pgno);
如果是第一次获取页,则应判断journal日志文是否存,如果存在则需要执行回滚操作
将journal日志文件中的备份,依次读入,检查校验和,如果正确,写入页文件fd
回滚结束后,应删除jfd文件
首先从页缓存的哈希表里查找,如果找到,页引用计数加1,函数返回
如果在缓存中找不到相应的页,则需要分配页缓存,从外存读取,初始化是否injournal的标识
此时应注意页缓存满的情况:
如果不存在引用计数为零的页缓存,则仍然分配页缓存.
如果存在引用计数为零的页缓存,则淘汰该页缓存,用于装载新的页,此时需要将此页写回fd,
优先取那些同步过jfd的空闲页缓存,如果没有,则同步jfd,同时从哈希表里删除相应的记录
将页号与页缓存指针添加进哈希表
如果页处在空闲链表中,脱链
如果页处在同步过jfd的空闲链表中,脱链
页引用计数加1
/**
* @brief 取消对当前页的引用 与PagerGetPage是对偶的操作
*/
extern int PagerReleasePage(HPAGE_HEAD pg);
页引用计数减1,
如果页的引用计数为零,则可以将它加入空闲页缓存链表,如果该页并且已经同步过jfd,则可以加入空闲的jfd缓存链表,
并且需要调用用户的release回调函数通知上层
/**
* @brief 获取页内容的缓冲区,用于写入
*/
extern void * PageHeadMakeWritable(HPAGE_HEAD pg);
判断jfd机制是否已启用,如果未启用则应该打开jfd文件,并分配着色表
判断此页是否已经处在jfd中了(从着色表里查),
如果没有则需要写入jfd,先页号,再写页缓存内容,最后写入校验和,并且给该页缓存做上injournal标识,并在着色表相应的位置置标识
将页缓存置成dirty,加入dirty链表.
如果页号比当前总页数大,
此时应将页文件的总页数增1
返回页缓存的首地址供上层写入(注意越界检测代码加入)
/**
* @brief 获取某一个页内容,用于读取
*/
extern const void * PageHeadMakeReadable(HPAGE_HEAD pg);
返回页缓存的首地址供上层读取
/**
* @brief 获取某一页的用户数据
*/
extern void * PageHeadGetUserData(HPAGE_HEAD pg);
用户调用 PagerReleasePage 时,上层不一定需要做初始化标识置成1
即当:
该页缓存被淘汰
该页缓存被重新加载,
此时,上层是需要重新做初始化的.
即Pager主动更改了页缓存的内容,则上层必须重新初始化
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -