zram:基于压缩的RAM块设备
介绍
zram模块创建名为/dev/zram<id>
(<id>
= 0, 1, ...)的基于RAM的块设备。写入这些磁盘的页面会被压缩并存储在内存中。这些磁盘允许非常快速的I/O,并且压缩提供了很好的内存节省。一些用例包括/tmp存储、用作交换磁盘、/var下的各种缓存,以及可能还有其他用途。 😃
单个zram设备的统计信息通过/sys/block/zram<id>/
下的sysfs节点导出。
用法
有几种方法可以配置和管理zram设备:
- 使用zram和zram_control sysfs属性
- 使用util-linux提供的zramctl实用程序(util-linux@vger.kernel.org)
在本文档中,我们将仅描述“手动”zram配置步骤,即zram和zram_control sysfs属性。
为了更好地了解zramctl,请参阅util-linux文档、zramctl手册页面或zramctl --help。请注意,zram的维护者不开发/维护util-linux或zramctl,如果您有任何问题,请联系util-linux@vger.kernel.org。
以下是使用zram的典型步骤序列。
警告
为了简单起见,我们在下面的大多数示例中省略了错误检查部分。但是,处理错误是您自己的责任。
在发生错误时,zram sysfs属性始终返回负值。可能的返回代码列表如下:
- -EBUSY:尝试修改无法在设备初始化后更改的属性。请先重置设备。
- -ENOMEM:zram无法分配足够的内存来满足您的需求。
- -EINVAL:提供了无效的输入。
如果您使用'echo',则返回的值由'echo'实用程序设置,在一般情况下,可以使用以下类似的代码来处理错误:
echo 3 > /sys/block/zram0/max_comp_streams if [ $? -ne 0 ]; then 处理错误 fi
1) 加载模块
modprobe zram num_devices=4
这将创建4个设备:/dev/zram{0,1,2,3}
num_devices参数是可选的,告诉zram应预先创建多少个设备。默认值为1。
2) 设置最大压缩流的数量
无论传递给此属性的值如何,ZRAM始终会分配多个压缩流 - 每个在线CPU一个 - 从而允许多个并发的压缩操作。当一些CPU下线时,分配的压缩流数量会减少。除非您运行的是UP系统或只有1个CPU在线,否则不再存在单一压缩流模式。
要查看当前有多少个流可用:
cat /sys/block/zram0/max_comp_streams
3) 选择压缩算法
使用comp_algorithm设备属性,可以查看可用的压缩算法和当前选择的(用方括号显示),或更改所选的压缩算法(一旦设备初始化,就无法更改压缩算法)。
示例:
# 显示支持的压缩算法 cat /sys/block/zram0/comp_algorithm lzo [lz4] # 选择lzo压缩算法 echo lzo > /sys/block/zram0/comp_algorithm
目前,comp_algorithm内容不一定显示内核支持的每种压缩算法。我们主要保留此列表以简化设备配置,可以使用comp_algorithm配置不在列表中的压缩算法的新设备。问题在于,内部使用Crypto API,如果某些算法被构建为模块,就不可能使用例如/proc/crypto或任何其他方法列出所有这些算法。然而,这样做的好处是允许使用自定义的加密压缩模块(实现S/W或H/W压缩)。
4) 设置磁盘大小
通过将值写入sysfs节点'disksize'来设置磁盘大小。该值可以是字节,也可以使用内存后缀。示例:
# 使用50MB的磁盘大小初始化/dev/zram0 echo $((50*1024*1024)) > /sys/block/zram0/disksize # 使用内存后缀 echo 256K > /sys/block/zram0/disksize echo 512M > /sys/block/zram0/disksize echo 1G > /sys/block/zram0/disksize
注意:创建大于内存大小两倍的zram几乎没有意义,因为我们期望有2:1的压缩比。请注意,当不使用zram时,zram使用的磁盘大小约为磁盘大小的0.1%,因此巨大的zram是浪费的。
5) 设置内存限制:可选
通过将值写入sysfs节点'mem_limit'来设置内存限制。该值可以是字节,也可以使用内存后缀。此外,您可以在运行时更改该值。示例:
# 限制/dev/zram0的内存为50MB echo $((50*1024*1024)) > /sys/block/zram0/mem_limit # 使用内存后缀 echo 256K > /sys/block/zram0/mem_limit echo 512M > /sys/block/zram0/mem_limit echo 1G > /sys/block/zram0/mem_limit # 禁用内存限制 echo 0 > /sys/block/zram0/mem_limit
6) 激活
mkswap /dev/zram0 swapon /dev/zram0 mkfs.ext4 /dev/zram1 mount /dev/zram1 /tmp
7) 添加/移除zram设备
zram提供了一个控制接口,可以实现动态(按需)添加和移除设备。
要添加新的/dev/zramX设备,请对hot_add属性执行读操作。这将返回新设备的设备ID(意味着您可以使用/dev/zram<id>
)或错误代码。
示例:
cat /sys/class/zram-control/hot_add 1
要移除现有的/dev/zramX设备(其中X是设备ID),执行:
echo X > /sys/class/zram-control/hot_remove
8) 统计信息
每个设备的统计信息都以不同的节点形式导出到 /sys/block/zram<id>/
下。
以下是导出设备属性的简要描述。更多细节请阅读 Documentation/ABI/testing/sysfs-block-zram。
- disksize:读写属性,显示和设置设备的磁盘大小
- initstate:只读属性,显示设备的初始化状态
- reset:写入属性,触发设备重置
- mem_used_max:写入属性,重置 mem_used_max 计数器(见后文)
- mem_limit:写入属性,指定 ZRAM 可用于存储压缩数据的最大内存量
- writeback_limit:写入属性,指定 zram 可以写出到后备设备的最大写入 IO 量(以 4KB 为单位)
- writeback_limit_enable:读写属性,显示和设置 writeback_limit 功能
- max_comp_streams:读写属性,可能的并发压缩操作数
- comp_algorithm:读写属性,显示和更改压缩算法
- compact:写入属性,触发内存压缩
- debug_stat:只读属性,用于 zram 调试目的
- backing_dev:读写属性,为 zram 设置后端存储以写出
- idle:写入属性,将分配的插槽标记为空闲
建议用户空间使用以下文件来读取设备统计信息。
- 文件
/sys/block/zram<id>/stat
:表示块层统计信息。有关详细信息,请阅读/sys/block/<dev>/stat
中的块层统计信息。 - 文件
/sys/block/zram<id>/io_stat
:表示设备的 I/O 统计信息,不计入块层统计,因此在zram<id>/stat
文件中不可用。它由一行文本组成,包含以下由空格分隔的统计信息:
- failed_reads:失败读取次数
- failed_writes:失败写入次数
- invalid_io:非页面大小对齐的 I/O 请求次数
- notify_free:根据设备使用场景,可能会计入以下内容:
- 由于交换槽空闲通知而释放的页面数
- 由 bio 发送的 REQ_OP_DISCARD 请求而释放的页面数。前者在交换块设备上发送,表示正在使用该磁盘作为交换磁盘。
- 后者是由挂载了 discard 选项的文件系统发送的,每当一些数据块被丢弃时。
- 文件
/sys/block/zram<id>/mm_stat
:表示设备的 mm 统计信息。它由一行文本组成,包含以下由空格分隔的统计信息:
- orig_data_size:存储在该磁盘中的未压缩数据大小。单位:字节
- compr_data_size:存储在该磁盘中的压缩数据大小
- mem_used_total:为该磁盘分配的内存量。这包括为该磁盘分配的分配器碎片和元数据开销。因此,可以使用 compr_data_size 和此统计信息来计算分配器空间效率。单位:字节
- mem_limit:ZRAM 可用于存储压缩数据的最大内存量
- mem_used_max:zram 用于存储数据的最大内存量
- same_pages:写入到该磁盘的相同元素填充页面数。这些页面不分配内存。
- pages_compacted:压缩期间释放的页面数
- huge_pages:不可压缩页面数
- huge_pages_since:自 zram 设置以来的不可压缩页面数
- 文件
/sys/block/zram<id>/bd_stat
:表示设备的后备设备统计信息。它由一行文本组成,包含以下由空格分隔的统计信息:
- bd_count:写入到后备设备的数据大小。单位:4K 字节
- bd_reads:从后备设备读取的次数。单位:4K 字节
- bd_writes:向后备设备写入的次数。单位:4K 字节
9) 停用
swapoff /dev/zram0 umount /dev/zram1
10) 重置
写入任何正值到 'reset' sysfs 节点:
echo 1 > /sys/block/zram0/reset echo 1 > /sys/block/zram1/reset
这将释放为给定设备分配的所有内存,并将磁盘大小重置为零。在重新使用设备之前,您必须重新设置磁盘大小。
可选的特性
回写
CONFIG_ZRAM_WRITEBACK是Linux内核中zram模块的一个可选功能,它允许zram将空闲/不可压缩的页面写入后备存储而不是保留在内存中。要使用此功能,管理员应该通过以下命令设置后备设备:
echo /dev/sda5 > /sys/block/zramX/backing_dev
在设置磁盘大小之前。目前,该功能仅支持分区。如果管理员想要使用不可压缩页面写回,可以通过以下命令实现:
echo huge > /sys/block/zramX/writeback
要使用空闲页面写回,用户首先需要将zram页面声明为空闲:
echo all > /sys/block/zramX/idle
从现在开始,zram上的任何页面都是空闲页面。除非有访问请求,否则这些页面仍然是空闲页面。此外,当启用CONFIG_ZRAM_MEMORY_TRACKING时,页面可以根据它们自上次访问以来的时间(以秒为单位)被标记为空闲:
echo 86400 > /sys/block/zramX/idle
在此示例中,超过86400秒(一天)未被访问的所有页面将被标记为空闲。
管理员可以通过以下命令在适当的时间请求这些空闲页面的写回:
echo idle > /sys/block/zramX/writeback
使用该命令,zram将从内存中将空闲页面写回到存储设备。
此外,如果用户选择仅写回巨大和空闲页面,可以通过以下命令实现:
echo huge_idle > /sys/block/zramX/writeback
如果管理员选择仅写回不可压缩页面(即无法通过任何算法进行压缩的页面),可以通过以下命令实现:
echo incompressible > /sys/block/zramX/writeback
如果管理员想要将zram设备中的特定页面写入后备设备,可以将页面索引写入接口:
echo "page_index=1251" > /sys/block/zramX/writeback
如果使用闪存设备进行大量写入操作,可能会出现闪存耗尽的问题,因此管理员需要设计写入限制以保证整个产品寿命的存储健康。
为了解决这个问题,zram支持"writeback_limit"功能。 "writeback_limit_enable"的默认值为0,因此不限制任何写回。如果管理员想要应用写回预算,他们应该通过以下命令启用writeback_limit_enable:
echo 1 > /sys/block/zramX/writeback_limit_enable
一旦设置了writeback_limit_enable,zram将不允许任何写回,直到管理员通过/sys/block/zramX/writeback_limit设置预算。
(如果管理员不启用writeback_limit_enable,则通过/sys/block/zramX/writeback_limit分配的值是无意义的。)
如果管理员希望将写回限制为每天400M,可以通过以下命令实现:
MB_SHIFT=20 4K_SHIFT=12 echo $((400<<MB_SHIFT>>4K_SHIFT)) > /sys/block/zram0/writeback_limit. echo 1 > /sys/block/zram0/writeback_limit_enable
如果管理员希望在预算用尽后再次允许进一步写入,可以通过以下命令实现:
echo $((400<<MB_SHIFT>>4K_SHIFT)) > /sys/block/zram0/writeback_limit
如果管理员想要查看自上次设置以来剩余的写回预算:
cat /sys/block/zramX/writeback_limit
如果管理员想要禁用写回限制,可以通过以下命令实现:
echo 0 > /sys/block/zramX/writeback_limit_enable
每当重置zram(例如系统重新启动,echo 1 > /sys/block/zramX/reset)时,writeback_limit计数都会重置,因此保留多少写回发生直到重置zram以在下一个设置中分配额外的写回预算是用户的工作。
如果管理员想要在一定时期内测量写回计数,可以通过/sys/block/zram0/bd_stat的第三列了解。
重新压缩
通过CONFIG_ZRAM_MULTI_COMP,zram可以使用备用(次要)压缩算法重新压缩页面。基本思想是备用压缩算法可以以更好的压缩比提供更慢的压缩/解压速度。备用压缩算法可以更成功地压缩巨大页面(默认算法无法压缩的页面)。另一个应用是空闲页面的重新压缩-在内存中冷却并且空闲的页面可以使用更有效的算法进行重新压缩,从而减少zsmalloc内存使用量。
通过CONFIG_ZRAM_MULTI_COMP,zram支持最多4种压缩算法:一个主要算法和最多3个次要算法。主要的zram压缩器在“3)选择压缩算法”中解释,次要算法是使用recomp_algorithm设备属性进行配置的。
示例:
#显示支持的重新压缩算法 cat /sys/block/zramX/recomp_algorithm #1: lzo lzo-rle lz4 lz4hc [zstd] #2: lzo lzo-rle lz4 [lz4hc] zstd
备用压缩算法按优先级排序。在上面的示例中,zstd被用作第一个备用算法,其优先级为1,而lz4hc被配置为优先级为2的压缩算法。在算法配置期间,提供了备用压缩算法的优先级:
#选择zstd重新压缩算法,优先级1 echo "algo=zstd priority=1" > /sys/block/zramX/recomp_algorithm #选择deflate重新压缩算法,优先级2 echo "algo=deflate priority=2" > /sys/block/zramX/recomp_algorithm
CONFIG_ZRAM_MULTI_COMP启用的另一个设备属性是recompress,它控制重新压缩。
示例:
#通过`idle`模式激活空闲页面重新压缩 echo "type=idle" > /sys/block/zramX/recompress #通过`huge`模式激活巨大页面重新压缩 echo "type=huge" > /sys/block/zram0/recompress #通过`huge_idle`模式激活巨大空闲页面重新压缩 echo "type=huge_idle" > /sys/block/zramX/recompress
空闲页面的数量可能很大,因此用户空间可以将大小阈值(以字节为单位)传递给recompress旋钮:zram将仅重新压缩大小相等或更大的页面:
#重新压缩所有大于3000字节的页面 echo "threshold=3000" > /sys/block/zramX/recompress #重新压缩大于2000字节的空闲页面 echo "type=idle threshold=2000" > /sys/block/zramX/recompress
空闲页面的重新压缩需要内存跟踪。
在重新压缩期间,对于每个符合重新压缩条件的页面,ZRAM按照它们的优先级顺序遍历注册的备用压缩算法列表。ZRAM会在重新压缩成功(重新压缩对象的大小小于原始对象)并符合重新压缩条件(例如大小阈值)时停止,或者当没有剩余的次要算法可尝试时停止。如果没有次要算法可以成功地重新压缩页面,则该页面被标记为不可压缩,因此ZRAM将不会尝试在将来重新压缩它。
当它遍历注册的压缩算法列表时,这种重新压缩行为增加了我们找到成功压缩特定页面的算法的机会。然而,有时限制重新压缩为仅使用一个特定算法是方便的(有时甚至是必要的),以便它不会尝试任何其他算法。这可以通过提供algo=NAME参数来实现:
#仅使用zstd算法(如果已注册) echo "type=huge algo=zstd" > /sys/block/zramX/recompress
内存跟踪
通过CONFIG_ZRAM_MEMORY_TRACKING,用户可以了解zram块的信息。这对于捕获具有*pagemap的进程的冷或不可压缩页面可能很有用。
如果启用了该功能,您可以通过/sys/kernel/debug/zram/zram0/block_state
查看块状态。输出如下所示:
300 75.033841 .wh... 301 63.806904 s..... 302 63.806919 ..hi.. 303 62.801919 ....r. 304 146.781902 ..hi.n
第一列
zram的块索引。
第二列
自系统启动以来的访问时间
第三列
块的状态:
- s:
相同页面 - w:
将页面写入后备存储 - h:
巨大页面 - i:
空闲页面 - r:
重新压缩页面(次要压缩算法) - n:
没有(包括次要)算法可以对其进行压缩
上面示例的第一行表示第300个块在75.033841秒时被访问,并且该块的状态是巨大的,因此它被写回到后备存储。这是一个调试功能,因此任何人都不应该依赖它正常工作。