页高速缓存和页回写
嵌入式学习Day6
1.先说页高速缓存是什么
Linux 读普通文件时,通常不是每次都直接访问磁盘,而是先看内存里有没有这页文件数据。如果有就称为缓存命中 比如你读一个文件的第 0~4095 字节:
- 内核会把这部分文件内容按“页”组织
- 一页通常是 4KB
- 这页会被放进 page cache
以后再读这部分内容时:
- 如果已经在页缓存中,直接从内存拿
- 不用再访问磁盘
2.页高速缓存 缓存的是什么
它缓存的是文件的内容页,不是进程私有匿名页 1)文件页(file-backed page)
- 可执行文件映射
- 普通文件 read/write
- mmap 文件
2)匿名页(anonymous page)
- malloc 之后得到的堆内存
- 用户栈
- brk 扩展出来的内存
3.页高速缓存放在哪里
从内核视角,文件有一个 address_space 对象,里面管理这个文件对应的页缓存。
文件 -> inode -> address_space -> cached pages
4.当进程执行 write() 往磁盘文件写数据时,页高速缓存到底怎么参与
目前有三种写缓存策略
4.1不缓存(nowrite)’
- 写操作不经过缓存
- 直接把数据写到磁盘
- 同时让原来缓存里的那份数据失效
4.2 写透缓存(write-through)
写操作既更新内存缓存,也同时更新磁盘文件。 也就是:
- 缓存要改
- 磁盘也要立刻改
4.3 回写(write-back)
Linux 采用的主要方式
- 写操作先只改页缓存
- 不立刻写磁盘
- 把这个页标成 脏页(dirty page)
- 之后由后台机制再写回磁盘
5.缓存回收策略
5.1缓存回收的目标
- 清除缓存中的旧数据
- 给更重要的新缓存项腾位置
- 必要时缩小缓存,占出内存给别的用途
5.2 回收规则
- 干净页优先回收——————因为磁盘里有备份,扔了还能再读回来
- 脏页不能直接回收——————要先写回磁盘。
- 理想策略是“扔未来最不会再用的页”
5.3 最近最少使用(LRU)策略
理想策略是难以实现的,所以对于通用的回收算法,最常用的就是LRU
- 最近刚被访问过的页,将来更可能再被访问
- 很久没被访问的页,将来不太可能再被访问
LRU 被一次性访问污染缓存
5.4 linux系统改进的LRU
但在操作系统里,页访问是极其频繁的:
- 每次内存访问都做重排,成本太高
- 多核并发下维护一个全局精确链表也很重
Linux系统将其缓存回收机制设置为一个“两级候选机制” 第一级:inactive list 新页、冷页、可疑页先放这里。 第二级:active list 只有那些被证明更有复用价值的页,才进入这里。 于是回收时:
- 先扫 inactive
- 真没价值的先扔
页怎么从 inactive 升到 active?
- 一个页第一次进入缓存,先在 inactive
- 如果之后又被访问到,说明它不是“一次性页”
- 那它更有资格进入 active
这就解决了LRU之前害怕访问大文件是别一次性访问污染的问题
6. address_space结构体
address_space 代表一个“可分页缓存的对象”的页缓存空间
文件 -> inode -> address_space -> cached pages
你可以把 address_space 理解成某个文件在内存里的“页缓存目录”他记录着:
- 这个文件有哪些页已经在缓存里
- 每一页对应文件的哪个偏移
- 哪些页是脏的
- 哪些页正在回写
- 怎么把页写回磁盘
6.1 为什么需要address_space结构体
从文件看来,数据是线性的,但在磁盘上,这些数据未必连续存放。比如文件的第 0 页逻辑上是连续 4KB,但底层磁盘块可能分散在不同位置。所以页缓存不能简单按“设备名 + 物理块号”来索引。
这时候就要回忆一下之前的块IO层了,使用IO读取数据的时候是使用设备名+物理块号来索引的
