深入了解Linux内核--页面回收和页交换

概述

可换出页

只有少量几种页可以换出到交换区,对其他页来说,换出到块设备上与之对应的后备存储器即可,如下所述。

类别为 MAP_ANONYMOUS 的页,没有关联到文件,例如,这可能是进程的栈或是使用 mmap 匿名映射的内存区。

进程的私有映射用于映射修改后不向底层块设备回写的文件,通常换出到交换区。

所有属于进程堆以及使用 malloc 分配的页

用于实现某种进程间通信机制的页。例如,用于在进程之间交换数据的共享内存页。

页颠簸

这个问题涉及交换区和物理内存之间密集的数据传输问题归结为页的来回、反复的交换。在系统进程的数目增加时,这种现象发生的几率也会增加。在换出重要的数据后不久再次需要该数据时,会发生这种现象。

为防止页颠簸,内核必须解决的主要的问题是,尽可能精确地确定一个进程的工作集 (working set,即使用最频繁的那些内存页),将最不重要的那些页移到交换区或其他后备存储器,而真正重要的页则一直驻留在内存中。

页交换算法

第二次机会

FIFO的改进版

LRU算法

least recently used:最近最少使用

Linux内核中的页面交换和回收

交换区的组织

换出的页或者保存在一个没有文件系统的专用分区中,或者存储在某个现存文件系统中的一个定长文件中。

还可以根据各个交换区的速度不,为其指定优先级。内核使用交换区时可以根据优先级进行选择。

每个交换区都细分为若干连续的槽(slot),每个槽的长度刚好与系统的一个页帧相同。在大多数处理器上,是4 KiB。但较新的系统通常会使用更大的页。

本质上,系统中的任何一页都可以容纳到交换区的任一槽中。但内核还使用了一种称为聚集(clustering)的构造法,使得能够尽快访问交换区。进程内存区中连续的页(或至少是连续换出的页)将按照特定的聚集大小(通常是256页)逐一写到硬盘上。如果交换区中没有更多空间可容纳此长度的聚集,内核可以使用其他任何位置上的空闲槽位。

如果使用了几个优先级相同的交换区,内核将使用一种循环进程来确保尽可能均匀地利用各个交换区。如果交换区的优先级不同,内核首先使用高优先级的交换区,然后逐渐转移到优先级较低的交换区。

为跟踪内存页在交换分区中的位置,内核必须维护一些数据结构,将该信息保存在内存中。结构中,最重要的数据成员是一个位图,用于跟踪交换区中各槽位的使用/空闲状态。其他成员包含的数据用于支持选择接下来使用的槽位,以及聚集的实现。

检查内存使用情况

选择要换出的页

内核混合使用了此前讨论的思想,实现了一种粗粒度的LRU方法,只使用了一种硬件特性,即在访问一页之后设置一个访问位,该功能在内核支持的所有体系结构上都可用,而且还可以毫不费力地进行仿真。

与通用的算法相比,内核对LRU的实现基于两个链表,分别称为活动链表和惰性链表(系统中的每个内存域都有这样的两个链表)。顾名思义,所有处于活动使用状态的页在一个链表上,而所有惰性页则保存在另一个链表上,这些页虽然可能映射到一个或多个进程,但不经常使用。为在两个链表之间分配页,内核需要定期执行均衡操作,通过上述访问位来确定一页是活动的还是惰性的,换言之,即该页是否经常被系统中的应用程序访问。页在两个链表之间可能会发生双向转移。页可以从活动链表转移到惰性链表,反之亦然。但这种转移不是每访问一页都会发生,它发生的时间间隔会比较长。

随着时间的推移,最不常用的页将收集到惰性链表的末尾。在出现内存不足时,内核将选择换出这些页。因为这些页到换出时,一直都很少使用,所以根据LRU原理,换出这些页对系统的破坏是最小的。

处理缺页异常

Linux运行的所有体系结构都支持缺页异常的概念,当访问虚拟地址空间中的一页,但该页不在物理内存中的时候,将触发缺页异常。缺页异常通知内核从交换区和其他的后备存储器读取缺失的数据。

缩减内核缓存

现在,用于缩减各种缓存的方法仍然是分别实现的,因为各种缓存的结构有很大的不同,很难采用一种通用的缓存收缩算法。但现在内核提供了一种通用框架,来管理各种缓存收缩方法。用于缩减缓存的函数在内核中称为收缩器(shrinker),可以动态注册。在缺乏内存时,内核将调用所有注册的收缩器来获得内存。

管理交换区

数据结构

交换区管理的基石是 mm/swap-info.c 中定义的swap_info数组,其中各数组项存储了关于系统中各个交换区的信息:

static struct swap_info_struct swap_info[MAX_SWAPFILES];//MAX_SWAPFILES默认32

内核使用交换文件(swap file)这个术语时,不仅是指用于页交换的文件,还包括交换分区,因此上述数组包括了这两种类型。

交换区的特征

<linux/swap.h>
struct swap_info_struct {
    unsigned int flags;
    int prio;           /* swap priority */
    struct file *swap_file;
    struct block_device *bdev;
    struct list_head extent_list;
    struct swap_extent *curr_swap_extent;
    unsigned old_block_size;
    unsigned short * swap_map;
    unsigned int lowest_bit;
    unsigned int highest_bit;
    unsigned int cluster_next;
    unsigned int cluster_nr;
    unsigned int pages;
    unsigned int max;
    unsigned int inuse_pages;
    int next;           /* next entry on swap list */
};

flags描述交换区的状态。SWP_USED 表明当前项在交换数组中处于使用状态。SWP_WRITEOK 指定当前项对应的交换区可写。在交换区插入到内核之后,这两个标志都会设置;二者合并后的缩写是 SWP_ACTIVE。

swap_file 指向与该交换区关联的 file 结构。对于交换分区,这是一个指向块设备上分区的设备文件的指针(在我们的例子中, /dev/hda5的情形即如此)。对于交换文件,该指针指向相关文件的 file 实例,即例子中 /mnt/swap1 或 /tmp/swap2 的情形。

bdev 指向文件/分区所在底层块设备的 block_device 结构。

交换区的相对优先级保存在 prio 成员中。

pages 保存了交换区中可用槽位的总数

max 保存了交换区当前包含的页数。不同于 pages ,该成员不仅计算可用的槽位,而且包括那些(例如,因为块设备故障)损坏或用于管理目的的槽位。

swap_map 是一个指针,指向一个短整型数组,其中包含的项数与交换区槽位数目相同。该数组用作一个访问计数器,每个数组项都表示共享对应换出页的进程的数目。

next 成员来建立一个相对的顺序

内核还在 mm/swapfile.c 中定义了全局变量 swap_list。 它是 swap_list_t 数据类型的一个实例,该类型是专门为查找第一个(高优先级)交换区而定义的

为了减少扫描整个交换区查找空闲槽位的搜索时间,内核借助 lowest_bit 和 highest_bit 成员,来管理搜索区域的下界和上界。在 lowest_bit 之下和 highest_bit 之上,是没有空闲槽位的,因而搜索相关区域是无意义的。

内核还提供了两个成员,分别是 cluster_next 和 cluster_nr ,以实现上文简要提到的聚集技术。前者指定了在交换区中接下来使用的槽位(在某个现存聚集中)的索引,而 cluster_nr表示当前聚集中仍然可用的槽位数,在消耗了这些空闲槽位之后,则必须建立一个新的聚集,否则(如果没有足够空闲槽位可用于建立新的聚集)就只能进行细粒度分配了(即不再按聚集分配槽位)。

用于实现非连续交换区的区间

在使用文件作为交换区时,情况会更复杂,因为无法保证文件的所有块在磁盘上是连续的。

因此将各个不连续块定义为区间

区间结构 struct swap_extent 定义如下:

<linux/swap.h>
struct swap_extent {
    struct list_head list;
    pgoff_t start_page;
    pgoff_t nr_pages;
    sector_t start_block;
};

list 是一个链表元素,用于将区间结构置于一个标准双链表上进行管理

区间中第一个槽位的编号保存在 start_page 中。

nr_pages 指定了区间中可容纳页的数目。

start_block 是区间的第一块在硬盘上的块号。

swap_info_struct 中一个额外的成员 curr_swap_extent 用于保存一个指针,指向区间链表中上一次访问的 swap_extent 实例。每次新的搜索都从该实例开始。因为通常是对连续的槽位进行访问,所搜索的块通常会位于该区间或下一个区间。

创建交换区

mkswap命令:

将所需交换区的长度除以所述机器的页长度,以确定其中能够容纳的页数。

逐一检查交换区的各个磁盘块是否有读写错误,以确定有缺陷的区域。因为交换区的页长度将使用机器的页长度,因而一个坏块就意味着交换区的容量减少了一页。

将一个包含所有坏块地址的列表,写入到交换区的第一页。

为向内核标识此类交换区,在第一页末尾设置了 SWAPSPACE2 标记。

可用槽位数目也存储在交换区头部。该值是通过从总的可用槽位数目中减去坏块数目而得到的。还必须从中减去1,因为第一页用于存储状态信息和坏块列表。

激活交换区

swapon->sys_swapon:

第一步,内核在 swap_info 数组中查找一个空闲数组项,并向该项指定初始值。如果将一个块设备分区用作交换区,则用 bd_claim 获取相关的 block_device 实例。

在已经打开交换文件(或交换分区)之后,读入第一页包含的坏块信息和交换区的长度。

setup_swap_extents 初始化区间链表。

最后一步,根据新交换区的优先级,将其添加到交换区的列表。

【文章福利】小编推荐自己的Linux内核技术交流群: 【977878001】整理一些个人觉得比较好得学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100进群领取,额外赠送一份 价值699的内核资料包(含视频教程、电子书、实战项目及代码)

内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料

学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈

交换缓存

内核利用了另一个缓存,称为交换缓存(swap cache),该缓存在选择换出页的操作和实际执行页交换的机制之间,充当协调者。

在页面选择策略和用于在内存和交换区之间传输数据的机制之间,交换缓存充当代理人的角色。这两个部分通过交换缓存交互。一方的输入会触发另一方的相应操作。不过请注意,对无须换出但可以同步的页来说,策略例程是可以直接与回写例程交互的。

在换出页时,页面选择逻辑首先选择一个适当的、很少使用的页帧。该页帧缓冲在页缓存中,然后将其转移到交换缓存。

如果换出页由几个进程在同时使用,内核必须设置进程页目录中的对应页表项,使之指向交换文件中相关的位置。在其中某个进程访问该页的数据时,该页将再次换入,该进程对应此页的页表项将设置为该页当前的内存地址。但是,这会导致一个问题。其他进程的对应页表项仍然指向交换文件中的位置,因为尽管可以确定共享一页的进程数目,却不可能确定具体是哪些进程在共享该页。

因而在换入共享页时,它们将停留在交换缓存中,直至所有进程都已经从交换区请求该页,并都知道了该页在内存中新的位置为止。

没有交换缓存的帮助,内核不能确定一个共享的内存页是否已经换入内存,将不可避免地导致对数据的冗余读取。

在换出共享页时, rmap 查找引用该页数据的所有进程。因而,引用该页的所有进程中的相关页表项都可以更新,指向交换区中对应的位置。这意味着,该页的数据可以立即换出,而无须在交换缓存中保持很长一段时间。

标记换出页

在换出页的页表项中,所有CPU都会存储下列信息。

一个标志,表明页已经换出。

该页所在交换区的编号。

对应槽位的偏移量,用于在交换区中查找该页所在的槽位。

<swap.h>
typedef struct {
    unsigned long val;
}swp_entry_t;

由于交换缓存仅仅是一个使用 long 作为键值的页缓存,换出页可以用这种方法唯一标识。

由于这种情形在未来可能发生改变,因而没有直接使用 unsigned long 值,而是将其隐藏到一个结构中。因为 swap_entry_t 值的内容只能通过专用函数访问,即使未来的内核版本修改页表项的内部表示,也无须重写页交换的实现。

<linux/swapops.h>
#define SWP_TYPE_SHIFT(e)   (sizeof(e.val) * 8 - MAX_SWAPFILES_SHIFT) //MAX_SWAPFILES_SHIFT = 5
#define SWP_OFFSET_MASK(e)  ((1UL << SWP_TYPE_SHIFT(e)) - 1)
static inline unsigned swp_type(swp_entry_t entry)
{
    return (entry.val >> SWP_TYPE_SHIFT(entry));
}
static inline pgoff_t swp_offset(swp_entry_t entry)
{
    return entry.val & SWP_OFFSET_MASK(entry);
}
static inline swp_entry_t swp_entry(unsigned long type, pgoff_t offset)//根据给定的偏移量和和类型,生成一个swp_entry_t
{
    swp_entry_t ret;
    ret.val = (type << SWP_TYPE_SHIFT(ret)) |
            (offset & SWP_OFFSET_MASK(ret));
    return ret;
}

交换缓存的结构

就数据结构而言,交换缓存不过是一个页缓存。其实现的核心是 swapper_space对象。

<mm/swap_state.c>
static const struct address_space_operations swap_aops = {
    .writepage  = swap_writepage,
    .sync_page  = block_sync_page,
    .set_page_dirty = __set_page_dirty_nobuffers,
    .migratepage    = migrate_page,
};
struct address_space swapper_space = {
    .page_tree  = RADIX_TREE_INIT(GFP_ATOMIC|__GFP_NOWARN),
    .tree_lock  = __RW_LOCK_UNLOCKED(swapper_space.tree_lock),
    .a_ops      = &swap_aops,
    .i_mmap_nonlinear = LIST_HEAD_INIT(swapper_space.i_mmap_nonlinear),
    .backing_dev_info = &swap_backing_dev_info,
};

内核提供了一组交换缓存访问函数,可以由任何

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值