内存
从一次访存动作说起
一次访存
cpu访问一个内存地址开始,导致都经历了什么样的过程?我将尝试阐述我的理解,欢迎讨论/aboutme;
流程为:
cpu发出访存指令
查找tlb
找不到发出tlb refill例外,无效,则发出tlb invalid例外,写但只读则tlb modified例外,让操作系统处理
查找cache
没有则读内存,内存也没有,则发出缺页中断
例外
略,在操作系统方面来处理
缺页中断
这个部分的逻辑处理主要在do_page_fault进入。
首先查找vma,确定权限,是否越界等,然后交付给handle_mm_fault来处理
handle_mm_fault则临时允许用户程序超出cgroup限制,并记录之后交给__handle_mm_fault识别类型,初始化一个struct vm_fault来记录缺页中断的一些信息,并查询页表填充进去,之后把这个结构体作为参数,交给handle_pte_fault进行处理。
handle_pte_fault则是具体根据缺页中断的具体类型,分别调用不同的处理函数。
场景 | pte | vm_ops | 类型 | 处理函数 |
页不在内存中 | pte为空 | 有 | 文件映射缺页中断 | do_fault |
无 | 匿名页面缺页中断 | do_anonymous_page | ||
存在 | 页被交换到swap分区 | do_swap_page | ||
页在内存中 | 写时复制 | do_wp_page |
swap
这里主要讲讲介绍其他资料比较少的swap的操作,
这里就要多介绍一个区域,swapcache。在内存和交换区中间充当缓冲。在因为页面回收的时候把页换出到交换区之前,页会先被记录在这里,直到换出操作执行完,而不是直接换出。换入的时候则是先读入到这里,然后后续需要再更新页表,设置为可用。(因为内核并不知道一个共享页是否被换入,可能会导致重复的读入。这样的话就不必换入的时候全部cpu都通知到,在需要的时候给他们更新信息即可,也不会重复)。
do_swap_page开始进入,首先查找swapcache,如果没有的话,就去swapin_readahead读入。尝试锁定返回的页,如果没有成功则说明读取还没成功,返回值中加入VM_FAULT_RETRY,返回,他会一直返回到do_page_fault中,会因为这个返回值而识别到还没成功而retry,重新走一遍上面的流程,直到读取完毕。锁定之后,对页面进行处理,包括cgroup的限制,页表更新,页面的映射等处理。
swapin_readahead,是主要的换入处理逻辑所在。在上面查找swapcache的时候会在全局有个原子变量记录距离上一次换入命中了多少次,然后swapin_nr_pages根据这个变量调整这次读入的数量。随后read_swap_cache_async来调用读入对其的连续的页。这里会用一个blk_*_plug来合并这些连续请求,以期加速。
交换区的读写
swap_readpage
swap_writepage
检查之后,首先查看frontswap是否使用,使用的话会使用frontswap的接口,失败的话才会进入到访问交换区设备,一般为硬盘设备,当然,最近也有一些使用rdma来做swap的,都是类似重写swap_readpage的,具体可以参考我的其他博客。类似Infiniswap是使用block device interface构建一个块设备做成交换区来实现的,leap和fastswap的则是类似于使用修改的frontswap的接口来实现的。
内存管理
上面讲述了操作系统如何和硬件一起完成一次访存操作。操作系统算是一个被动的一方。下面讲述操作系统是怎么管理内存的,这里是讲述其主动的一方面。
有空再续
参考资料
linux-4.11.0代码
《奔跑吧!linux内核》
《计算机体系结构》胡伟武