在第一次接触zram这个名词的时候有点懵,究竟什么是zram呢?其实zram就是linux内核中内存压缩模块,那么问题又来了,什么是内存压缩呢?

内存压缩

术语:

Active memory

Active memory:活跃的内存,比方可以说是开个5个App,每个占用了100M,那么就有500M活跃内存;

Inactive memory

Inactive memory:非活跃的内存,比如关掉了三个App,那么这三个App占用的共300M内存就不再是活跃了,但是内核还会保留它。如果这时立马再启动这三个App,就会以非常快的速度打开了,因为还没有被其他App占用的Inactive memory这时又被激活了。如果很长一段时间内这些非活跃内存都没有再用的话,随着系统内存占用增加,这些非活跃内存也会被清空而用做新的用途。

Compressed Memory的作用

那么Compressed Memory在其中扮演什么角色呢?很简单,它尽可能久的保留那些会用到的非活跃内存,以使系统更快的响应潜在的第二次响应。但是为了照顾新的内存需求,内核会压缩这些非活跃内存以腾出空间,以供新的App使用。

这里写图片描述

技术特点

  1. 缩减了内存使用:很明显,压缩了非活跃内存;
  2. 改善了电源效能:在内存不足的情况下,优先使用压缩非活跃内存技术,而非把内存存为SWAP到硬盘上,减少了硬盘IO带来的能量损耗;
  3. 最小化CPU使用:正如上面一样,通过CPU压缩或解压内存是非常快速的一件事情,也减少了CPU处理其他任务的损耗;
  4. 多核支持:传统的虚拟内存、或写SWAP等操作都是单核的,但是压缩内存技术则是可以并行跑在多核上,同时压缩内存和开辟新的内存空间

zram的使用

在调研期间,尝试过各种方法,最终发现一个好的方法。

创建zramswap.conf 文件

/etc/init目录下,创建zramswap.conf,文本内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
description "Initializes zram swaping"
start on runlevel [2345]
stop on runlevel [!2345]
pre-start script
# load dependency modules
modprobe zram num_devices=2
# initialize the devices
echo 1073741824 > /sys/block/zram0/disksize
echo 1073741824 > /sys/block/zram1/disksize
# Creating swap filesystems
mkswap /dev/zram0
mkswap /dev/zram1
# Switch the swaps on
swapon -p 5 /dev/zram0
swapon -p 5 /dev/zram1
end script
post-stop script
# Switching off swap
swapoff /dev/zram0
swapoff /dev/zram1
rmmod zram
end script

重启计算机

查看swap的信息

  1. grep Swap /proc/meminfo see total swap, and free swap (all linux)
  2. cat /proc/swaps see which swap devices are being used (all linux)
  3. swapon -s see swap devices and sizes (where swapon is installed)

验证zram的使用

说明运行环境,物理内存8个G,设置zram0和zram1两个块设备的大小为1个G,测试程序申请7个G的内存,测试程序源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>
int main()
{
//printf("%d\n", sizeof(int));
int *mem;
int i, size;
size = 0x70000000;
mem = (int*)malloc(size*sizeof(int));
for(i = 0; i < size; i++)
mem[i] = (i%1024);
getchar();
free(mem);
return 0;
}

测试程序运行前,zram0和zram1的使用情况如下图:

这里写图片描述

测试程序运行后,zram0和zram1的使用情况如下图:

这里写图片描述

从以上两张图看出zram已经成功使用了!

zram源码解析

对于zram源码的解析,魅族内核团队已经写了技术博客。
zram源码

对于块设备基础概念和块设备驱动整体框架的相关知识,请读者自己查阅相关资料。

zram 架构

zram 从架构上可以分为三部分:

  • 驱动部分

该部分创建了一个块设备,然后提供了处理 IO 请求的接口;

  • 数据流操作部分

该部分主要提供串行或者并行的压缩和解压操作;

  • 解压缩算法部分

该部分主要是一个个压缩和解压算法,每个算法都提供统一的压缩和解压接口给数据流操作部分调用。

这里写图片描述

zram 驱动部分代码分析

  • zram_init

首先调用 register_blkdev 注册块设备驱动到内核中,然后再根据 num_devices 调用 create_device 来创建相应个数的块设备, 这里默认是创建一个块设备。

这里写图片描述

  • create_device

对于 flash、 RAM 等完全随机访问的非机械设备,并不需要进行复杂的 I/O 调度,所以这里直接调用 blk_alloc_queue 分配一个 “请求队列”,然后使用 blk_queue_make_request 函数绑定分配好的 “请求队列” 和 “请求处理”函数 zram_make_request。接着初始化块设备的操作函数集 zram_devops 及设备容量、名字、队列等其他属性,最后调用 add_disk 将该块设备真正添加到内核中。

这里写图片描述

  • disksize_store

zram 使用了 Zsmalloc 分配器来管理它的内存空间,Zsmalloc 分配器尝试将多个相同大小的对象存放在组合页(称为 zspage)中,这个组合页不要求物理连续,从而提高内存的使用率。

首先会根据 zram 的内存中页面的个数,创建相应个数的 zram table,每个 zram table 都对应一个页面;然后会调用 zs_create_pool 创建一个 zsmalloc 的内存池,以后所有的页面申请和释放都是通过 zs_malloc 和 zs_free 来分配和释放相对应的对象。

这里写图片描述

  • zram_make_request

在整个块设备的 I/O 操作中,贯穿于始终的就是“请求”,块设备的 I/O 操作会排队和整合。块设备驱动的任务就是处理请求,对请求的排队和整合则是由 I/O 调度算法解决,因此,zram 块设备驱动的核心这个请求处理函数,所有的 zram I/O 请求都是通过这个请求处理函数来处理的。

首先它判断这个 I/O 请求是否是有效的,即检测请求是否在 zram 逻辑块的范围以内,且是否对齐。然后调用__zram_make_request 遍历 bio 中的每个段 bio_vec,根据 bio 的传输方向选择执行写 (zram_bvec_write) 或者读 (zram_bvec_read) 操作。

这里写图片描述

  • zram_bvec_write

在写数据之前,首先使用 GFP_NOIO 标志创建一个不允许任何 I/O 初始化的页面,然后将 zram_data 对应的数据先解压出来放到该创建的页面中。接着去调用 zcomp_strm_find 找到一个压缩操作流,如果是单压缩流,则实际调用的是 zcomp_strm_single_find,如果是多压缩流,则实际调用的是 zcomp_strm_multi_find。

然后,将段 bio_vec 中的页面临时映射到高端地址,并将高端地址空间页面的内容复制到已保存好 zram_data 压缩后的数据的页面。调用 zs_malloc 申请一个 zram table,使 zcomp_compress 压缩内容并将压缩后的内容存放到新申请的 zram table。最后调用 zram_free_page 删除旧内容所占用的 zram table。

zcomp_decompress 会根据 struct zcomp_backend 初始化时设定的压缩算法来调用相应的解压接口,lzo 压缩算法的解压接口是 lzo_compress ,而 lz4 压缩算法的解压接口是 zcomp_lz4_compress ,该接口还调用了压缩操作流,以此执行串行或者并行写操作。

这里写图片描述

  • zram_bvec_read

读操作首先将段 bio_vec 中的页面临时映射到高端地址,然后再调用 zram_decompress_page 将 zram_meta 所对应的数据解压到这块映射的高端内存空间,解压的接口是 zcomp_decompress,它会根据 struct zcomp_backend 初始化时设定的压缩算法来调用相应的解压接口,lzo 压缩算法的解压接口是 lzo_decompress ,而 lz4 压缩算法的解压接口是 zcomp_lz4_decompress 。

这里写图片描述

数据流操作部分代码分析

  • zcomp_create

若最大可能同时执行压缩操作的个数来调用为一,则调用 zcomp_strm_single_create 来创建一个压缩流,而若最大可能同时执行压缩操作的个数来调用大于一,则调用 zcomp_strm_multi_create 先创建一个压缩流,然后创建一个压缩流链表,并将创建好的压缩流加到压缩流链表中,后面再根据需求来动态创建更多的压缩流。


参考资料:

  1. imtx
  2. weirdfellow
  3. stackexchange
  4. 魅族
  5. kernel.org