内存管理-45-zram-2-实现分析

基于msm-5.4


一、简介

传统的 swap 机制是将数据交换到磁盘中,需要进行 I/O 操作,所以一定程度上会影响系统性能。但是建立在swap机制上的zRAM机制将压缩后的数据存储在内存中,对性能影响较小。

在 Linux-3.14 引入的zRAM,其原理是实现一个基于RAM的块设备,将那些暂时不用的内存数据进行压缩后存储在这个设备上。

zRAM机制是在低内存下工作,而其自身又要分配内存。zsmalloc 是专门为 ZRAM 设计的内存分配器,它尝试将多个相同大小的对象存放在组合页(称为 zspage)中,####并且这个组合页不要求物理连续,从而大大提高了内存的使用率,确保 ZRAM 在内存紧张的情况下也能稳定运行 。


二、内核相关配置宏

1.CONFIG_ZRAM

压缩 RAM 块设备支持配置。创建名为 /dev/block/zramX 的虚拟块设备(X = 0, 1, ...)。写入这些磁盘的页面会被压缩并存储在内存中。这些磁盘支持非常快速的 I/O,压缩可以节省大量内存。它有多种用途,例如:用作 /tmp 存储、swap磁盘等等。

2. CONFIG_ZRAM_WRITEBACK

将不可压缩或空闲的页面写回到后备存储设备。对于不可压缩页面,其保留在内存中不会节省内存空间。相反,将其写入后备存储设备。要使用此功能,管理员应通过 /sys/block/zramX/backing_dev 设置后备存储设备。
使用 /sys/block/zramX/{idle,writeback},应用程序可以请求空闲页面回写到后备存储设备,以将其保存在内存中。

3. CONFIG_ZRAM_MEMORY_TRACKING

借助此功能,管理员可以跟踪已分配 zRAM 块的状态。管理员可以通过 /sys/kernel/debug/zram/z​​ramX/block_state 查看相关信息。

代码位置:

msm-5.4/drivers/block/zram$ ls
Kconfig  Makefile  zcomp.c  zcomp.h  zram_drv.c  zram_drv.h


三、相关数据结构

1. struct zcomp_strm

struct zcomp_strm { //zcomp.h
    void *buffer;
    struct crypto_comp *tfm;
};

成员介绍:

buffer: 压缩和解压缩缓冲区。在 zcomp_compress() 中用于存放压缩后的数据。

tfm:


2. struct zcomp

struct zcomp { //zcomp.h
    struct zcomp_strm * __percpu *stream;
    const char *name;
    struct hlist_node node;
};

每设备的动态压缩前端。


3. struct zram_table_entry

struct zram_table_entry { //zram_drv.h
    union {
        unsigned long handle;
        unsigned long element;
    };
    unsigned long flags;
};

此结构作为 struct zram 中的一个数组,通过下标 index 记录与 handle 之间的映射关系。


4. struct zram_stats

struct zram_stats {
    atomic64_t compr_data_size;    /* compressed size of pages stored */
    atomic64_t num_reads;    /* failed + successful */
    atomic64_t num_writes;    /* --do-- */
    atomic64_t failed_reads;    /* can happen when memory is too low */
    atomic64_t failed_writes;    /* can happen when memory is too low */
    atomic64_t invalid_io;    /* non-page-aligned I/O requests */
    atomic64_t notify_free;    /* no. of swap slot free notifications */
    atomic64_t same_pages;        /* no. of same element filled pages */
    atomic64_t huge_pages;        /* no. of huge pages */
    atomic64_t pages_stored;    /* no. of pages currently stored */
    atomic_long_t max_used_pages;    /* no. of maximum pages stored */
    atomic64_t writestall;        /* no. of write slow paths */
    atomic64_t miss_free;        /* no. of missed free */
};


4. struct zram

struct zram { //zram_drv.h
    struct zram_table_entry *table;
    struct zs_pool *mem_pool;
    struct zcomp *comp;
    struct gendisk *disk;
    struct rw_semaphore init_lock;
    unsigned long limit_pages;
    struct zram_stats stats;
    u64 disksize;
    char compressor[CRYPTO_MAX_ALG_NAME]; //128
    bool claim;
    struct file *backing_dev;
};

它是一个每zram dev的结构,由 disksize_store() 可知存储在 struct gendisk::part0.__dev->private_data 中。

成员介绍:

limit_pages: 用于防止设备初始化时的并发执行。

disksize: 这是我们可以在磁盘中存储的*未压缩*数据量的限制, 单位字节。

claim: 表示 zram 已被声明,因此打开请求将失败。此成员受到 bdev->bd_mutex 的保护。


四、zram技术实现

zRAM 块设备驱动的实现代码主要在 drivers/block/zram/zram_drv.c 文件中,这里围绕它进行介绍。主要分析 swap 机制在进行内存交换时与 zRAM 块设备驱动的交互。

1. 压缩内存

当系统内存不足时,内核将会触发 swap 机制。swap 机制首先会从系统中选择一些进程不常用内存,然后将这些不常用的内存交换到 zRAM 块设备中(使用 zRAM 块设备作为交换设备的情况下)。

当 swap 机制将不常用的内存交换到 zRAM 块设备时,会调用 zram_make_request() 函数处理请求。而 zram_make_request() 最终会通过调用 zram_bvec_write() 函数来压缩内存,调用链如下:

zram_make_request
    __zram_make_request
        zram_bvec_rw
            zram_bvec_write

zram_bvec_write() 函数的实现如下:

static int zram_bvec_write(struct zram *zram, struct bio_vec *bvec, u32 index, int offset, struct bio *bio)
{
    /* 1.获取要压缩的page */
    src = kmap_atomic(bvec->bv_page);
    /* 2.对内存进行压缩 */
    zcomp_compress(zstrm, src, &comp_len);
    /* 3.获取压缩后的数据 */
    src = zstrm->buffer;
    /* 4.申请一个内存块保存压缩后的数据 */
    handle = zs_malloc(zram->mem_pool, comp_len, GFP_FLAGS);
    dst = zs_map_object(zram->mem_pool, handle, ZS_MM_WO);
    /* 5.将压缩后的数据保存到新申请的内存块中 */
    memcpy(dst, src, comp_len);
    /* 6. 将压缩后的数据记录到zRAM块设备的表格中 */
    zram->table[index].handle = handle;
}

zRAM 机制对内存进行压缩的步骤如下:

(1) 获取需要进行压缩的内存,由 swap 机制提供。
(2) 通过 zcomp_compress() 函数对内存进行压缩, src 是要压缩的数据,数据压缩后存储在 zstrm->buffer 中。
(3) 通过 zs_malloc() 和 zs_map_object() 函数申请一块新的内存块,大小为压缩后数据的大小。
(4) 将压缩后的数据复制到新申请的内存块中。
(5) 将压缩后的数据记录到 zRAM 块设备的表格中。

由于 zRAM 块设备是建立在内存中的虚拟块设备,所以其并没有真实块设备的特性。真实块设备会将存储空间划分成一个个块,而 zram_bvec_write() 函数的 index 参数就是数据块的编号,此参数由 swap 机制提供,所以 zRAM 块设备驱动通过 index 参数作为原始内存数据的编号。

zRAM驱动有个数据块表,用来记录原始内存数据对应的压缩数据,此表的索引就是数据块的编号。swap 机制会维护此表格的使用情况,如哪个块是空闲的,哪个块被占用等。

当内存页被压缩后,swap 机制将会把原来的内存页释放掉,并且把所有映射到此内存页的进程解除映射,细节可以参考 swap 机制相关的资料。


五、模块参数

1. num_devices

要启用 zRAM,首先需要创建 zRAM 块设备,可以使用以下命令:

modprobe zram num_devices=1

num_devices 参数可以指定创建 zRAM 块设备的个数,上面命令只创建了一个 zRAM 块设备,可以通过路径 /dev/block/zram0 来访问这个块设备。若是通过 "modprobe zram num_devices=4" 则会创建 /dev/block/zram{0,1,2,3} 4个设备。


六、sys文件节点

注: 各文件作用和实验见:https://www.cnblogs.com/hellokitty2/p/18824389

sys文件节点位于 /sys/block/zramX 目录下,这里只列出 zram 与其它block设备有差别的部分。各文件的含义详见 /msm-5.4/Documentation/ABI/testing/sysfs-block-zram。

1. compact

是只写的,并触发分配器 zrm 使用的压缩。分配器会移动一些对象,以便释放碎片空间。

触发内存压缩。

2. disksize

可读写的,它指定zRAM 块设备大小,该大小表示可以存储在该磁盘中的*uncompressed*数据的最大限制(是不是针对解压缩)。

echo 512M > /sys/block/zram0/disksize                //以MB为单位
echo $((512*1024*1024)) > /sys/block/zram0/disksize  //以字节为单位

上面命令设置了 zram0 的大小为 512MB,也就是说 zram0 能够存储 512MB 压缩后的数据。

3. mem_limit

是只写的,指定了 ZRAM 可用于存储压缩数据的最大内存量。此限制可在运行时更改,“0”表示禁用限制。初始状态为无限制。单位字节。

4. debug_stat

是只读文件,包含各种设备的调试信息,对内核开发者很有用。其格式并未刻意记录,并且可能随时更改。

5. initstate

initstate 文件是只读的,显示设备的初始化状态。

6. mem_used_max

是只写的,用于重置 zram 存储压缩数据所消耗的最大内存计数器。要重置该值,您应该写入“0”。否则,您可能会看到 -EINVAL。单位字节。

7. trace

8. dev

9. io_stat

是只读文件,用于累积设备未计入块层的 I/O 统计信息。例如,failed_reads、failed_writes 等。文件格式与块层统计文件格式类似。

10. mm_stat

mm_stat 文件是只读的,以类似于块层统计文件的格式表示设备的 mm 统计信息(orig_data_size、compr_data_size 等)。

11. comp_algorithm

可读写的,可以显示可用和选定的压缩算法,更改压缩算法选择。不同的压缩算法有不同的压缩比率和压缩速度,用户可以按照自身的需求来选择不同的压缩算法。

/sys/block/zram0 # cat comp_algorithm //查看支持的压缩算法
lzo lzo-rle [lz4] zstd
/sys/block/zram0 # echo lzo > comp_algorithm //设备初始化后不再支持更改压缩算法,需要先swapoff,然后再reset,然后才能设置
//报错: zram: Can't change algorithm for initialized device

12. idle

是只写文件,将 zram slot 标记为空闲。如果系统已挂载了 debugfs,用户可以通过 /sys/kernel/debug/zram/z​​ram<id>/block_state 查看哪些slot处于空闲状态。

13. max_comp_streams

是可读写的,指定后端的 zcomp_strm 压缩流的数量(并发压缩操作的数量), 也即最大可同时执行压缩操作的个数。

/sys/block/zram0 # echo 3 > max_comp_streams //正常启动后设置就不生效了
/sys/block/zram0 # cat max_comp_streams
8

在 Linux-3.15 版本之后 zram 压缩支持多个压缩流。cat 此文件可以查看压缩流的最大个数,echo 可进行设置。

14. reset

是只写的,允许重置设备。重置操作将释放与此设备相关的所有内存。

15. waiting_for_supplier

 

下面是默认配置下不存在的文件节点,这里也列出来:

16. backing_dev

可读写的,用于设置 zram 的备用设备,以便写入不可压缩的页面。要使用该文件,用户需要启用 CONFIG_ZRAM_WRITEBACK。

17. writeback

是只写的,触发空闲和/或大页面回写到后备存储设备上。

18. bd_stat

是只读的,以类似于块层统计文件的格式,表示支持设备的统计信息(bd_count、bd_reads、bd_writes)。

19. writeback_limit_enable

是可读写的,用于指定 writeback_limit 功能的启用状态。“1”表示启用该功能。“0”表示无限制,为初始状态。

20. writeback_limit

是可读写的,用于指定 ZRAM 的最大回写量,此限制可以在运行时更改。


七、zram配置流程

1. 步骤示例

# modprobe zram num_devices=1 //创建一个zram块设备 /dev/block/zram0
# echo 512M > /sys/block/zram0/disksize //设置zram块设备的大小
# echo lz4 > /sys/block/zram0/comp_algorithm //选择压缩算法
# mkswap /dev/zram0 //将swap交换区设置为zram0设备
# swapon /dev/zram0 //启动交换区

执行完上面命令后,内核将会使用 zram0 作为 swap 的交换设备。


八、相关调试

1. /proc/meminfo

# cat /proc/meminfo
SwapTotal:       6291452 kB
SwapFree:        6193972 kB

若是只有一个 zram0 设备,则 SwapTotal 的值是 cat /sys/block/zram0/disksize 得到的大小减去4K。


2. free -h

# free -h
                total        used        free      shared     buffers
Mem:              13G         11G        2.5G         50M        6.8M
-/+ buffers/cache:            11G        2.6G
Swap:            6.0G         95M        5.9G //交换分区的使用情况


2. dumpsys meminfo

# dumpsys meminfo | grep ZRAM -C 6
Total RAM: 13,853,312K (status normal)
 Free RAM: 5,908,691K (   63,535K cached pss + 2,795,512K cached kernel + 3,049,644K free)
      ION:   651,168K (  585,500K heap +    65,668K pools)
  DMA BUF: 1,135,908K (  555,344K mapped +   580,564K unmapped)
 Used RAM: 6,759,431K (4,821,619K used pss + 1,937,812K kernel)
 Lost RAM: 1,270,034K
     ZRAM:    23,056K physical used for   113,508K in swap (6,291,452K total swap) //这一列
   Tuning: 512 (large 512), oom   322,560K, restore limit   107,520K (high-end-gfx)

 

posted on 2025-04-14 14:03  Hello-World3  阅读(75)  评论(0)    收藏  举报

导航