页面回收内容感觉非常复杂,主要原因在于分析者,把其复杂化。
1.内存页面
页面回收实际就是回收相应缓存, 因为系统为了性能有效,使用大量缓存。 这些缓存占用大量内存。 当系统内存短缺时,可以回收相应页面。如果不一致话,首先要把缓存内容,刷新到外部存储中。缓存页面可分为:可压缩的缓存(目录和inode节点占用内存页面), slab, 交换高速缓存(缓存交换分区的页面),页高速缓存(缓存文件内容的页面)。由于可压缩的缓存和 slab占用页面,可有专门函数进行回收,所以只讨论其他可回收的页面。
由此可见页面回收主要就是交换高速缓存与页高速缓存页面的回收。系统中匿名映射对应于交换高速缓存, 文件映射对应于页高速缓存页面, 可以说存在第三种磁盘缓冲区页(实际包含在页高速缓存页面), 这些缓冲区信息包含(磁盘inode, 超级块等设备信息数据, 因为这些数据不是已文件方式组织,以块方式存放在硬盘), 我们讨论页面回收就是回收这三方面.
为了加深理解,了解进程内存映射。内存要么缓存文件,要么缓存匿名数据
# pmap 24044
24044: bash
0000000000400000 852K r-x-- /bin/bash
00000000006d4000 40K rw--- /bin/bash
00000000006de000 20K rw--- [ anon ]
0000000002509000 396K rw--- [ anon ]
00007ffb3c562000 48K r-x-- /lib64/libnss_files-2.12.so
00007ffb3c56e000 2048K ----- /lib64/libnss_files-2.12.so
00007ffb3c76e000 4K r---- /lib64/libnss_files-2.12.so
2.页面回收实现
页面回收实现,实际就是断开其他数据结构对于页描述的引用,同时断开页描述符中相应字段对于其他结构引用。一句话双方数据结构引用相互断开。
页描述符数据结构:
struct page {
unsigned long flags; /* Atomic flags, some possibly updated asynchronously */
atomic_t _count; /* Usage count, see below. */
atomic_t _mapcount; /* Count of ptes mapped in mms,
* to show when page is mapped
* & limit reverse map searches.
*/
union {
struct {
unsigned long private; /* Mapping-private opaque data:
* usually used for buffer_heads
* if PagePrivate set; used for
* swp_entry_t if PageSwapCache;
* indicates order in the buddy
* system if PG_buddy is set.
*/
struct address_space *mapping; /* If low bit clear, points to
* inode address_space, or NULL.
* If page mapped as anonymous
* memory, low bit is set, and
* it points to anon_vma object:
* see PAGE_MAPPING_ANON below.
*/
};
struct list_head lru; /* Pageout list, eg. active_list
* protected by zone->lru_lock !
*/
}
count: 反映物理页使用计数, 0表示空闲,没人用,释放。最简单内存回收实际可以这样实现,仅仅标记其0就可以^_^。
flags: 反映物理页状态,例如物理页脏,意味着物理内存页与后端设备内容不一致。如果不一致,页面回收该如何处理呢?
mapcount: 反映有多少个进程页表项指向该物理页, 如果多个页表项引用话,页面回收该如何处理呢?
private: 反映有无缓存区,如果有话,页面回收该如何处理呢?
mapping:反映是页高速缓存还是交换高速缓存
特别注意: private与mapping为union, 即两者不可兼得。
3.页面回收代码分析
过程如下:
一)页面统一处理:
1 如果多个页表项引用话,.断开对于页面引用。
2.页面内容不一致,进行页面回写
二)根据页面缓存内容类型,分别处理:
3.现在判断该页面磁盘缓冲区,还是交换高速缓存,还是页面高速缓存,当然只能为其一。
4.磁盘缓冲区:从缓冲区头,断开对页面指向。
5.交换高速缓存:从交换分区基树中,删除该页面。
6.页面高速缓存:从节点基树中,删除该页面。
三)释放页面,标记物理页使用计数为0等
7.收集要释放页面,之后统一存放内存分配区,释放完成。
static unsigned long shrink_page_list(struct list_head *page_list,
struct scan_control *sc,
enum pageout_io sync_writeback)
{
while (!list_empty(page_list)) {
//如果有多少个进程页表项指向该物理页, 如果多个页表项引用话, 需要断开其映射。
//实际就是断开指向页描述符,设置相应进程页表项为NULL,删除页描述所指向vma
if (page_mapped(page) && mapping) {
switch (try_to_unmap(page, TTU_UNMAP)) {
case SWAP_SUCCESS:
; /* try to free the page below */
}
}
//如果页描述符所指向页面脏(与硬盘上数据不一致)的话,所以回收之前,其内容写回到硬盘。
if (PageDirty(page)) {
switch (pageout(page, mapping, sync_writeback)) {
case PAGE_SUCCESS:
if (PageWriteback(page) || PageDirty(page))
goto keep;
/*
* A synchronous write - probably a ramdisk. Go
* ahead and try to reclaim the page.
*/
if (!trylock_page(page))
goto keep;
if (PageDirty(page) || PageWriteback(page))
goto keep_locked;
mapping = page_mapping(page);
case PAGE_CLEAN:
; /* try to free the page below */
}
}
//磁盘缓冲区: 如果页描述符所指向缓冲区不为空话,断开页描述与其关联。
if (page_has_private(page)) {
if (!try_to_release_page(page, sc->gfp_mask))
goto activate_locked;
if (!mapping && page_count(page) == 1) {
unlock_page(page);
if (put_page_testzero(page))
goto free_it;
else {
/*
* rare race with speculative reference.
* the speculative reference will free
* this page shortly, so we may
* increment nr_reclaimed here (and
* leave it off the LRU).
*/
nr_reclaimed++;
continue;
}
}
}
//回收页高速缓冲和交换高速缓冲页面,实际从两者基树中删除
if (!mapping || !__remove_mapping(mapping, page))
goto keep_locked;
__clear_page_locked(page);
//收集要释放页面
free_it:
nr_reclaimed++;
if (!pagevec_add(&freed_pvec, page)) {
__pagevec_free(&freed_pvec);
pagevec_reinit(&freed_pvec);
}
continue;
}
//集中释放收集的页面
list_splice(&ret_pages, page_list);
if (pagevec_count(&freed_pvec))
__pagevec_free(&freed_pvec);
count_vm_events(PGACTIVATE, pgactivate);
trace_mm_pagereclaim_free(nr_reclaimed);
return nr_reclaimed;
}
1.内存页面
页面回收实际就是回收相应缓存, 因为系统为了性能有效,使用大量缓存。 这些缓存占用大量内存。 当系统内存短缺时,可以回收相应页面。如果不一致话,首先要把缓存内容,刷新到外部存储中。缓存页面可分为:可压缩的缓存(目录和inode节点占用内存页面), slab, 交换高速缓存(缓存交换分区的页面),页高速缓存(缓存文件内容的页面)。由于可压缩的缓存和 slab占用页面,可有专门函数进行回收,所以只讨论其他可回收的页面。
由此可见页面回收主要就是交换高速缓存与页高速缓存页面的回收。系统中匿名映射对应于交换高速缓存, 文件映射对应于页高速缓存页面, 可以说存在第三种磁盘缓冲区页(实际包含在页高速缓存页面), 这些缓冲区信息包含(磁盘inode, 超级块等设备信息数据, 因为这些数据不是已文件方式组织,以块方式存放在硬盘), 我们讨论页面回收就是回收这三方面.
为了加深理解,了解进程内存映射。内存要么缓存文件,要么缓存匿名数据
# pmap 24044
24044: bash
0000000000400000 852K r-x-- /bin/bash
00000000006d4000 40K rw--- /bin/bash
00000000006de000 20K rw--- [ anon ]
0000000002509000 396K rw--- [ anon ]
00007ffb3c562000 48K r-x-- /lib64/libnss_files-2.12.so
00007ffb3c56e000 2048K ----- /lib64/libnss_files-2.12.so
00007ffb3c76e000 4K r---- /lib64/libnss_files-2.12.so
2.页面回收实现
页面回收实现,实际就是断开其他数据结构对于页描述的引用,同时断开页描述符中相应字段对于其他结构引用。一句话双方数据结构引用相互断开。
页描述符数据结构:
struct page {
unsigned long flags; /* Atomic flags, some possibly updated asynchronously */
atomic_t _count; /* Usage count, see below. */
atomic_t _mapcount; /* Count of ptes mapped in mms,
* to show when page is mapped
* & limit reverse map searches.
*/
union {
struct {
unsigned long private; /* Mapping-private opaque data:
* usually used for buffer_heads
* if PagePrivate set; used for
* swp_entry_t if PageSwapCache;
* indicates order in the buddy
* system if PG_buddy is set.
*/
struct address_space *mapping; /* If low bit clear, points to
* inode address_space, or NULL.
* If page mapped as anonymous
* memory, low bit is set, and
* it points to anon_vma object:
* see PAGE_MAPPING_ANON below.
*/
};
struct list_head lru; /* Pageout list, eg. active_list
* protected by zone->lru_lock !
*/
}
count: 反映物理页使用计数, 0表示空闲,没人用,释放。最简单内存回收实际可以这样实现,仅仅标记其0就可以^_^。
flags: 反映物理页状态,例如物理页脏,意味着物理内存页与后端设备内容不一致。如果不一致,页面回收该如何处理呢?
mapcount: 反映有多少个进程页表项指向该物理页, 如果多个页表项引用话,页面回收该如何处理呢?
private: 反映有无缓存区,如果有话,页面回收该如何处理呢?
mapping:反映是页高速缓存还是交换高速缓存
特别注意: private与mapping为union, 即两者不可兼得。
3.页面回收代码分析
过程如下:
一)页面统一处理:
1 如果多个页表项引用话,.断开对于页面引用。
2.页面内容不一致,进行页面回写
二)根据页面缓存内容类型,分别处理:
3.现在判断该页面磁盘缓冲区,还是交换高速缓存,还是页面高速缓存,当然只能为其一。
4.磁盘缓冲区:从缓冲区头,断开对页面指向。
5.交换高速缓存:从交换分区基树中,删除该页面。
6.页面高速缓存:从节点基树中,删除该页面。
三)释放页面,标记物理页使用计数为0等
7.收集要释放页面,之后统一存放内存分配区,释放完成。
static unsigned long shrink_page_list(struct list_head *page_list,
struct scan_control *sc,
enum pageout_io sync_writeback)
{
while (!list_empty(page_list)) {
//如果有多少个进程页表项指向该物理页, 如果多个页表项引用话, 需要断开其映射。
//实际就是断开指向页描述符,设置相应进程页表项为NULL,删除页描述所指向vma
if (page_mapped(page) && mapping) {
switch (try_to_unmap(page, TTU_UNMAP)) {
case SWAP_SUCCESS:
; /* try to free the page below */
}
}
//如果页描述符所指向页面脏(与硬盘上数据不一致)的话,所以回收之前,其内容写回到硬盘。
if (PageDirty(page)) {
switch (pageout(page, mapping, sync_writeback)) {
case PAGE_SUCCESS:
if (PageWriteback(page) || PageDirty(page))
goto keep;
/*
* A synchronous write - probably a ramdisk. Go
* ahead and try to reclaim the page.
*/
if (!trylock_page(page))
goto keep;
if (PageDirty(page) || PageWriteback(page))
goto keep_locked;
mapping = page_mapping(page);
case PAGE_CLEAN:
; /* try to free the page below */
}
}
//磁盘缓冲区: 如果页描述符所指向缓冲区不为空话,断开页描述与其关联。
if (page_has_private(page)) {
if (!try_to_release_page(page, sc->gfp_mask))
goto activate_locked;
if (!mapping && page_count(page) == 1) {
unlock_page(page);
if (put_page_testzero(page))
goto free_it;
else {
/*
* rare race with speculative reference.
* the speculative reference will free
* this page shortly, so we may
* increment nr_reclaimed here (and
* leave it off the LRU).
*/
nr_reclaimed++;
continue;
}
}
}
//回收页高速缓冲和交换高速缓冲页面,实际从两者基树中删除
if (!mapping || !__remove_mapping(mapping, page))
goto keep_locked;
__clear_page_locked(page);
//收集要释放页面
free_it:
nr_reclaimed++;
if (!pagevec_add(&freed_pvec, page)) {
__pagevec_free(&freed_pvec);
pagevec_reinit(&freed_pvec);
}
continue;
}
//集中释放收集的页面
list_splice(&ret_pages, page_list);
if (pagevec_count(&freed_pvec))
__pagevec_free(&freed_pvec);
count_vm_events(PGACTIVATE, pgactivate);
trace_mm_pagereclaim_free(nr_reclaimed);
return nr_reclaimed;
}