zram 技术的由来:
zram(也称为 zRAM,先前称为 compcache)是 Linux 内核的一项功能,可提供虚拟内存压缩。zram 通过在 RAM 内的压缩块设备上分页,直到必须使用硬盘上的交换空间,以避免在磁盘上进行分页,从而提高性能。由于 zram 可以用内存替代硬盘为系统提供交换空间的功能,zram 可以在需要交换 / 分页时让 Linux 更好利用 RAM ,在物理内存较少的旧电脑上尤其如此。
即使 RAM 的价格相对较低,zram 仍有利于嵌入式设备、上网本和其它相似的低端硬件设备。这些设备通常使用固态存储,它们由于其固有性质而寿命有限,因而避免以其提供交换空间可防止其迅速磨损。此外,使用 zRAM 还可显著降低 Linux 系统用于交换的 I/O 。
zram 在 2009 年的时候就进了 kernel 的 staging 目录,并于 2014 年 3 月 30 日发布的 3.14 版本正式合并入 Linux 内核主线。在 2014 年 6 月 8 日发布的 3.15 版本的 Linux 内核中,zram 已可支持 LZ4 压缩算法,而 LZO 仍然作为默认的压缩后端。内核 3.15 中的修改还改进了性能,以及经由 sysfs 切换压缩算法的能力。
ubuntu 于 13.10 开始使用 zram 。截至 2012 年 12 月,Ubuntu 考虑为小内存的计算机默认启用 zram 。 Google 在 Chrome OS 中使用 zram,它也成为了 Android 4.4 及以后版本设备的一个选项。
本文主要介绍在 Android 设备上使用的 zram swap,它可以让小内存的设备在多任务的情况下切换自如,提高用户体验。
zram swap 主要原理就是从内存分配一块区域出来用作 swap 分区,每次如果内存空间不够了,不是把应用程序杀掉,而是把应用程序所占用的内存数据复制到 swap 分区,等切换回来的时候就可以直接把这部分数据恢复到内存当中,节省重新开启所需的时间。而被放到 swap 分区的应用程序,所占用的内存都是被压缩过的,比如,微信在普通内存中占用 50 MB 的空间,如果压缩率为 0.4,则放到 swap 分区里面的数据只需要 20 MB 的空间,这样 swap 分区里面就可以存放更多后台临时不用的应用程序,变相扩展了内存的大小。
zram 配置步骤:
3.15 之前版本的 kernel
Device Drivers -> Staging drivers (STAGING [=y])
3.15 及之后版本的 kernel
Device Drivers -> [] Block devices -> Compressed RAM block device support
具体的配置项如下:
CONFIG_RESOURCE_COUNTERS=y
CONFIG_MEMCG=y
CONFIG_MEMCG_SWAP=y
CONFIG_MEMCG_SWAP_ENABLED=y
CONFIG_MEMCG_KMEM=y
CONFIG_ZRAM=y
CONFIG_TOI_ZRAM_SUPPORT=y
CONFIG_ZRAM_DEBUG=y
zram 块设备个数设定
如果是将 zram 编译成模块,则可以使用下面命令动态加载,这个命令会创建 4 个设备 /dev/zram{0,1,2,3}
modprobe zram num_devices=4
如果是直接将 zram 编译到内核,那只能在代码里面直接修改 num_devices,3.15 之前的版本代码路径是 drivers/staging/zram/zram_drv.c,3.15 及之后的版本代码路径是 ./drivers/block/zram/zram_drv.c ,默认 zram 设备个数是一个。
压缩流的最大个数设定
这个是 3.15 版本及以后的 kernel 新加入的功能,3.15 版本之前的 zram 压缩都是使用一个压缩流(缓存 buffer 和算法私有部分)实现,每个写(压缩)操作都会独享压缩流,但是单压缩流如果出现数据奔溃或者卡住的现象,所有的写(压缩)操作将一直处于等待状态,这样效率非常低;而多压缩流的架构会让写(压缩)操作可以并行去执行,大大提高了压缩的效率和稳定性
查看压缩流的最大个数,默认是 1
cat /sys/block/zram0/max_comp_streams
设定压缩流的最大个数
echo 3 > /sys/block/zram0/max_comp_streams
压缩算法选择
查看目前支持的压缩算法
cat /sys/block/zram0/comp_algorithm
lzo [lz4]
修改压缩算法
echo lzo > /sys/block/zram0/comp_algorithm
zram 内存大小设定
分配部分内存作为 zram ,大小建议为总内存的 10%-25%
可以使用数值直接设置内存大小,单位是 bytes
echo $((51210241024)) > /sys/block/zram0/disksize
也可以使用带内存单位作为后缀的方式设置内存大小
echo 256K > /sys/block/zram0/disksize
echo 512M > /sys/block/zram0/disksize
echo 1G > /sys/block/zram0/disksize
启用 zram 设备为 swap
mkswap /dev/zram0
swapon /dev/zram0
具体的 zram 相关对外接口说明
Name Access Description
disksize RW 显示和设置该块设备的内存大小
initstate RO 显示设备的初始化状态
reset WO 重置设备
num_reads RO 读数据的个数
failed_reads RO 读数据失败的个数
num_write RO 写数据的个数
failed_writes RO 写数据失败的个数
invalid_io RO 非页面大小对齐的I/O请求的个数
max_comp_streams RW 最大可能同时执行压缩操作的个数
comp_algorithm RW 显示和设置压缩算法
notify_free RO 空闲内存的通知个数
zero_pages RO 写入该块设备的全为的页面的个数
orig_data_size RO 保存在该块设备中没有被压缩的数据的大小
compr_data_size RO 保存在该块设备中已被压缩的数据的大小
mem_used_total RO 分配给该块设备的总内存大小
mem_used_max RW 该块设备已用的内存大小,可以写 1 重置这个计数参数到当前真实的统计值
mem_limit RW zram 可以用来保存压缩数据的最大内存
pages_compacted RO 在压缩过程中可用的空闲页面的个数
compact WO 触发内存压缩
reset zram
reset zram后,zram的大小就会变为0,在使用之前必须需要设置大小
echo 1 > /sys/block/zram0/reset
改变zram的大小
如果zram的大小比较小,不能满足需要。需要就该zram的大小。
root@test:/data # echo $((51210241024)) > /sys/block/zram0/disksize
sh: echo: write error: Device or resource busy
如果直接设置新的大小,就会提示设置失败,设备正在使用。所以先需要关闭设备。
root@test:/data # cat /proc/swaps
Filename Type Size Used Priority
/dev/zram0 partition 409596 4456 -1
root@test:/data # swapoff /dev/zram0
root@test:/data # cat /proc/swaps
Filename Type Size Used Priority
root@test:/data #
可以发现zram0已经不属于交换分区了。接着继续设置大小
root@test:/data # echo $((5121024*1024)) > /sys/block/zram0/disksize
sh: echo: write error: Device or resource busy
root@test:/data #
于是发现还是设置失败,设备正在使用。查看zram的驱动。
static ssize_t disksize_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t len)
{
…
if (init_done(zram)) {
pr_info(“Cannot change disksize for initialized device\n”);
err = -EBUSY;
goto out_destroy_comp;
}
}
可以发现init_done条件不成立,查看init_done函数。
static inline int init_done(struct zram *zram)
{
return zram->meta != NULL;
}
所以想要重新设置大小,在驱动中查看zram->meta在什么情况下为NULL
static void zram_reset_device(struct zram zram, bool reset_capacity)
{
…
zram_meta_free(zram->meta);
zram->meta = NULL;
/ Reset stats */
memset(&zram->stats, 0, sizeof(zram->stats));
zram->disksize = 0;
if (reset_capacity)
set_capacity(zram->disk, 0);
}
所以设置大小之前还需要执行reset操作。
root@test:/data # echo 1 > /sys/block/zram0/reset
root@test:/data # echo $((51210241024)) > /sys/block/zram0/disksize
root@test:/data #