页框回收与交换
概念
内核在为进程服务的过程中会分配大量的页,但是这些页对应的虚拟地址在进程的生命周期里一直会被断断续续的访问,所以当内核同时为大量进程服务时,内存终究会耗尽。所有页框回收就是在内核未耗尽内存之前(因为回收与交换也会使用内存),将在使用过程标记未访问不频繁的部分内存换出到磁盘,释放所占用的内存补给系统,以维持内核的正常运转,待被换出的页对应的虚拟地址再次被访问时,内核又通过缺页系统将换出的页再次分配新的内存进行换入,这样周而复始形成良性的循环。
组成
每个zone中都有一套
lru
和buddy system
- 页:可回收的页
- 最近使用链表
lru
:页描述符的标记和放置使用页的活动与不活动链表,使用过程中通过对页的访问,判断页是否是活动的,从而在活动与非活动链表中来回移动。 - 算法和守护线程:内存分配子系统中的页的直接回收路径与守护线程,他们负责计算应该将什么也标记为可回收状态、回收的数量是多少,然后触发或执行回收过程(扫描非活动链表开始回收)。
- 交换分区:交换分区页高速缓存与交换分区磁盘(或文件),他们负责暂存页引用和存储页的数据。
- 文件和设备:文件页高速缓存与文件(或设备),他们负责暂存页引用和回写脏页数据。
参与页框回收的页类型
type | comments | reclaimed operation |
---|---|---|
不可回收 | 1. 活动系统中的空闲页 2. 保留页( PG_reserved 置位,比如内核镜像页)3. 内核使用的动态分配的页 4. 进程内核态堆栈页 5. 临时锁定的页( PG_locked 置位) |
不允许回收或无需回收 |
可交换页 | 1. 用户态进程匿名页 2. tmpfs 文件系统的映射页(比如IPC 共享内存页) |
将页交换到交换分区 |
可同步页 | 1. 用户态进程映射页 2. 存有磁盘文件数据页且在文件页高速缓存中 3. 块设备缓冲区页 4. 磁盘高速缓存页(索引节点高速缓存) |
必要时,与磁盘同步这些页 |
可丢弃页 | 1. 内存高速缓存中未使用的页(比如slab 分配器的未使用对象缓存)2. 目录项高速缓存的未使用页 |
释放这些页,压缩缓存 |
页的转换图
- 每个zone中都有一套
lru
和buddy system
- 每个CPU有一套
lru-cache
- 页的转换
+--------------------+
+---->>>-----+--->| inactive lru cache |--->-+
^ ^ +--------------------+ |
| | |
| | v
| +-----+------+ +--------+-----+
| | active lru | | inactive lru |
| +-----+------+ +--------------+
| ^ |
| | |
+---->>>-----+--<<<--+ |
| | |
+---+-------+ +-----+------------+ v
| lru cache | | active lru cache | v
+---+-------+ +-----+------------+ v
^ | |
| | |
| | re-active |
PAGE | +-----<<<<<-------------+
| |
+-----+-----+ start reclaimed | +-----------------+
| buddy sys | +--------<<<<<<------+------->| unevictable lru |
+-----+-----+ | +-----------------+
^ FILE sync v ANON swap
| +----<<----+----->>---+
| | |
| v v
| +-----+-----+ +-----+-----+
| | page cache| | swap cache|
| +-----+-----+ +-----+-----+
| | |
| | reused page |
+----<<<-----+---------<<<---------+
v v
+-----+-----+ +-----+-----+
| file | | swap |
+-----+-----+ +-----+-----+
| |
+---->>----+----<<----+
|
v
+----+----+
|disk dev|
+---------+
lru
链表分类
type | comments |
---|---|
LRU_INACTIVE_ANON |
非活动的匿名映射页的lru |
LRU_INACTIVE_FILE |
非活动的文件映射页的lru |
LRU_ACTIVE_ANON |
活动的匿名映射页的lru |
LRU_ACTIVE_FILE |
活动的文件映射页的lru |
LRU_UNEVICTABLE |
不可回收页的lru |
lru
的per-CPU
缓存分类
type | comments |
---|---|
页高速缓存
数据结构
页高速缓存核心数据结构是address_space
对象,被嵌入在页所有者的索引节点对象中。每个页描述符中使用mapping
(索引节点的address_space
对象)和index
(页大小的磁盘镜像偏移)字段关联到页高速缓存中。
- 基树
struct radix_tree_root address_space.page_tree
是基树的根,由深度、节点概念构成。每个节点可以存储64个槽位,如果是叶子节点则存储内容为页描述符,否则为其他的子树节点的指针。假设有页号为0、4、131三页数据,那么在树中的映射为:
+-------+
| h = 2 |
+-------+
| rnode |
+---+---+
|
|
v
+------------------+
| count = 2 |
+------------------+
| 0 | | 2 |...| 63 |
+-+-----+----------+
| |
+-------+ +-------+
| |
v v
+------------------+ +------------------+
| count = 2 | | count = 1 |
+------------------+ +------------------+
| 0 | | 4 |...| 63 | | 0 | | 3 |...| 63 |
+-+-----+----------+ +-------+----------+
| | |
| +-------+ |
| | |
v v v
+-----+ +-----+ +-----+
|page | |page | |page |
+-----+ +-----+ +-----+
-
优先搜索树
-
反向匿名映射
-
操作方法接口
页高速缓存中的页通过const struct address_space_operations *a_ops
操作对外部子系统提供操作,主要方法如下:int (*writepage)(struct page *page, struct writeback_control *wbc)
写操作int (*readpage)(struct file *, struct page *)
读操作int (*write_begin)(struct file *, struct address_space *mapping, loff_t pos, unsigned len, unsigned flags, struct page **pagep, void **fsdata)
为写操作做准备int (*write_end)(struct file *, struct address_space *mapping, loff_t pos, unsigned len, unsigned copied, struct page *page, void *fsdata)
完成写操作
内部操作API
API | page | comments |
---|---|---|
find_get_page() |
get_page() |
根据偏移查找一页,然后增加页的引用 |
find_get_pages() |
get_page() |
根据起始偏移和请求数量,查找一组连续的页,然后对每一页都增加引用 |
find_lock_page() |
1. get_page() 2. lock_page() |
根据偏移查找一页,然后增加页的引用,并试图锁定改页(可能发生阻塞) |
add_to_page_cache_lru() |
1. get_page() 2. __SetPageLocked() 3. page.mapping = mapping 4. page.index = offset 5. lru_cache_add() |
1. 从外部分配新页。 2. 锁定页,因为是新页内容无效,防止其他的路径进行访问 3. 插入到页高速缓存中,并初始化页的高速缓存相关字段 4. 新缓存的可回收的页,插入 lru |