这一步是比较艰巨的,上一篇文章里虽然看上去都是fanyi的锅,实际上问题是我都是处理一些迁移好的依赖(by linuxkpi),而fanyi需要弄一个没有的依赖(没有那么好接,需要读一下源码)
到了我们这,Linux和FreeBSD的swap区别还是蛮大,我将会首先梳理Linux中是如何在swap之前先调用frontswap的,再梳理FreeBSD的swap到底是怎么运转的,最后再选择一种合理的方式(尽量照抄Linux)把frontswap接入到 BSD 中。
Frontswap的一探究竟
Frontswap作为一个中间层,为优化方式提供了一种接口,使得页面在真正换出到swap设备之前可以先进入frontswap,如果frontswap成功,则不必再换出。
从下层接口看起
struct frontswap_ops {
void (*init)(unsigned); /* this swap type was just swapon'ed */
int (*store)(unsigned, pgoff_t, struct page *); /* store a page */
int (*load)(unsigned, pgoff_t, struct page *); /* load a page */
void (*invalidate_page)(unsigned, pgoff_t); /* page no longer needed */
void (*invalidate_area)(unsigned); /* swap type just swapoff'ed */
};
这里没加参数名字,但是那个unsigned都是type, pgoff_t 都是offset就好了。现在frontswap只有一个driver,即zswap,所以我们都会结合zswap来说。
在我理解,一个type对应的是一个swap设备,或者说,我们每次做swapon系统调用的时候,那个对应的文件就是一个设备,当成一个逻辑设备就好了(毕竟万物皆文件嘛)。
在这种基础上,我们就很好理解了,frontswap要求driver可以区分不同的设备,根据不同的设备初始化、插入、加载和删除页面。以及可以失效一整个设备的所有页面。
上层接口
extern void frontswap_init(unsigned type, unsigned long *map);
extern int __frontswap_store(struct page *page);
extern int __frontswap_load(struct page *page);
extern void __frontswap_invalidate_page(unsigned, pgoff_t);
extern void __frontswap_invalidate_area(unsigned);
相对应的,frontswap也为上层提供了一系列接口,其中参数大差不差,store和load会提取page中的private字段,最后拆成type和offset,再塞给下层,这可能是我们要在过程中关注的一点,
- 一个页面在什么时候会获得这个private,
- 它是怎么跟pid和虚拟内存的offset对应上的?
我们在FreeBSD里如何把swap的vm_object和offset 跟这个private对应起来?
我们好像没有那么多地方能存放数据。总不能把zswap改成二重索引的树套树。
除此之外,frontswap还在对应swap设备的控制结构swap_info_struct中留下了一个unsigned long *类型的参数map,用于中间层校验当前的page是否在frontswap中存在,及相应的是否有效
上层接口到下层接口
1. Driver的注册
在做zswap的时候我们已经很明显的看到了,frontswap用register函数注册相关的控制结构
int frontswap_register_ops(const struct frontswap_ops *ops)
{
if (frontswap_ops)
return -EINVAL;
frontswap_ops = ops;
static_branch_inc(&frontswap_enabled_key);
return 0;
}
注册的过程伴随着swap设备的swapon,但是存在一种情况即backend driver还没准备好,我们就要往里写了,所以frontswap的每个操作都会在操作前首先检测注册情况
2. init
void frontswap_init(unsigned type, unsigned long *map)
{
struct swap_info_struct *sis = swap_info[type];
VM_BUG_ON(sis == NULL);
if (WARN_ON(!map))
return;
frontswap_map_set(sis, map);
if (!frontswap_enabled())
return;
frontswap_ops->init(type);
}
这个map是个bitmap,用来判断一个页面是不是在frontswap里,初始化的时候,要新建一个map赋给这个设备的swap_info
3. test & set & clear
听起来就像bit操作,实际上也确实是,这两个函数根据给定的swap_info和offset,把map的第offset位返回/置1/清除
4. store & load
终于到了比较核心的地方
int __frontswap_store(struct page *page)
{
int ret = -1;
swp_entry_t entry = { .val = page_private(page), };
int type = swp_type(entry);
struct swap_info_struct *sis = swap_info[type];
pgoff_t offset = swp_offset(entry);
if (__frontswap_test(sis, offset)) {
__frontswap_clear(sis, offset);
frontswap_ops->invalidate_page(type, offset);
}
ret = frontswap_ops->store(type, offset, page);
if (ret == 0) {
__frontswap_set(sis, offset);
inc_frontswap_succ_stores();
} else {
inc_frontswap_failed_stores();
}
return ret;
}
就和我们之前说的一样,首先从private中拆出来private字段,拆成type和offset,验证当前页面是否在frontswap中已经出现了,如果出现,首先干掉,然后再把页面塞进去
int __frontswap_load(struct page *page)
{
int ret = -1;
swp_entry_t entry = { .val = page_private(page), };
int type = swp_type(entry);
struct swap_info_struct *sis = swap_info[type];
pgoff_t offset = swp_offset(entry);
if (!__frontswap_test(sis, offset))
return -1;
ret = frontswap_ops->load(type, offset, page);
if (ret == 0)
inc_frontswap_loads();
return ret;
}
load也差不多,这边如果检测到不在,那就直接返回失败,否则load一下。
至于后面的两个invalid,我们等到他们被调用的时候再看好了。
那它在哪被调用呢?别急,我们先去看看Linux的Swap吧
Linux Swap的浅述
swap应该是大家都听过的概念,废话不多说,不管是linux还是unix,只有匿名页面在需要内存回收的时候,需要放在swap分区中,以便重新访问的时候调入内存,不然只能OOM Kill了,而以文件做backend的页面可能是不需要换出的(可读写页面可能会分情况,比如出现了CoW之后再设置为可以换出的页面)
什么时候发生swap?
- 周期性检查,保证内存有充足空间:mm/vmscan.c - kswapd()
- 直接内存回收,当内存不够用的时候: mm/page_alloc.c - __alloc_pages_slowpath
我们关心的第一个问题:swp_entry_t 从哪里来,又到哪里去
来看一张图
swp_entry就是一个long,和一个PTE等长,正好可以放进一个页表作为一个页表项,当swap out的时候,linux在回收物理页面之前先修改这个PTE,这样下次访问到这个PTE的时候就会触发一个Page Fault,再通过这个swp_entry_t找到原来这个页面在swap中的位置,进而换出,再把这个表项修改成真的PTE。
PTE的如下三种情况
- 全为0:该页不属于进程的地址空间,或相应的页框尚未分配给进程。
- 最低位为0,其余位不全为0:该页现在在swap分区中。
- 最低位为1:present位为1,表示该页在内存中。直接转换。
至于FreeBSD怎么办,我们一会再想,反正现在是知道这个entry是咋来的了
swap是怎么调用frontswap的?失败了怎么办?
什么时候init
SYSCALL_DEFINE2(swapon, const char __user *, specialfile, int, swap_flags) {
// ...
if (IS_ENABLED(CONFIG_FRONTSWAP))
frontswap_map = kvcalloc(BITS_TO_LONGS(maxpages),
sizeof(long),
GFP_KERNEL);
// ...
enable_swap_info(p, prio, swap_map, cluster_info, frontswap_map);
// ...
}
static void enable_swap_info(struct swap_info_struct *p, int prio,
unsigned char *swap_map,
struct swap_cluster_info *cluster_info,
unsigned long *frontswap_map)
{
if (IS_ENABLED(CONFIG_FRONTSWAP))
frontswap_init(p->type, frontswap_map);
}
swapon的时候,这个调用链路实在太明确啦,就不说了,
- 因此我们大概也是要找到FreeBSD对应的Swapon,幸运的是,这个应该不是很难。
什么时候Store
肯定是先Store再load啦,我们先看Store好了。
在mm/page_io.c下
int swap_writepage(struct page *page, struct writeback_control *wbc)
{
// some other code
if (frontswap_store(&folio->page) == 0) {
folio_start_writeback(folio);
folio_unlock(folio);
folio_end_writeback(folio);
return 0;
}
__swap_writepage(&folio->page, wbc);
return 0;
}
如果走frontswap成功,就不走后面的write_page了,调用start_writeback,然后结束流程。
然而这块很显然出现了一个问题,folio_xx_writeback是用来干啥的呢?按理说,我们是在swap之前就把该unmap的已经unmap掉了,最核心的写入部分也搞完了,应该只能有点收尾工作,不能有writeback啊?这个问题稍后再看,我们把frontswap看完
什么时候load
读取的部分是+号位有点长
void swap_readpage(struct page *page, bool synchronous, struct swap_iocb **plug)
{
struct swap_info_struct *sis = page_swap_info(page);
bool workingset = PageWorkingset(page);
unsigned long pflags;
bool in_thrashing;
VM_BUG_ON_PAGE(!PageSwapCache(page) && !synchronous, page);
VM_BUG_ON_PAGE(!PageLocked(page), page);
VM_BUG_ON_PAGE(PageUptodate(page), page);
/*
* Count submission time as memory stall and delay. When the device
* is congested, or the submitting cgroup IO-throttled, submission
* can be a significant part of overall IO time.
*/
if (workingset) {
delayacct_thrashing_start(&in_thrashing);
psi_memstall_enter(&pflags);
}
delayacct_swapin_start();
if (frontswap_load(page) == 0) {
SetPageUptodate(page);
unlock_page(page);
} else if (data_race(sis->flags & SWP_FS_OPS)) {
swap_readpage_fs(page, plug);
} else if (synchronous || (sis->flags & SWP_SYNCHRONOUS_IO)) {
swap_readpage_bdev_sync(page, sis);
} else {
swap_readpage_bdev_async(page, sis);
}
if (workingset) {
delayacct_thrashing_end(&in_thrashing);
psi_memstall_leave(&pflags);
}
delayacct_swapin_end();
}
别看这里多了几个if else,其实store只是把这几个if-else包了一层,如果失败自动回归原流程,好像也没啥需要特别注意的。
至于range_free(invalidate)什么的,我们倒是不着急,先把核心做出来吧.
评论 (0)