内存管理-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/zramX/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/zram<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) 收藏 举报