轉自:http://kernel.meizu.com/zram-introduction.html
zram 技術的由來
zram1(也稱為 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 切換壓縮算法的能力。
Lubuntu 於 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 配置步驟
1. 內核配置2
-
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
2. 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. 壓縮流的最大個數設定
這個是 3.15 版本及以后的 kernel 新加入的功能,3.15 版本之前的 zram 壓縮都是使用一個壓縮流(緩存 buffer 和算法私有部分)實現,每個寫(壓縮)操作都會獨享壓縮流,但是單壓縮流如果出現數據奔潰或者卡住的現象,所有的寫(壓縮)操作將一直處於等待狀態,這樣效率非常低;而多壓縮流的架構會讓寫(壓縮)操作可以並行去執行,大大提高了壓縮的效率和穩定性。
- 查看壓縮流的最大個數,默認是 1
cat /sys/block/zram0/max_comp_streams
- 設定壓縮流的最大個數
echo 3 > /sys/block/zram0/max_comp_streams
4. 壓縮算法選擇
- 查看目前支持的壓縮算法
cat /sys/block/zram0/comp_algorithm
lzo [lz4]
- 修改壓縮算法
echo lzo > /sys/block/zram0/comp_algorithm
5. zram 內存大小設定
分配部分內存作為 zram ,大小建議為總內存的 10%-25% 。
- 可以使用數值直接設置內存大小,單位是 bytes
echo $((512*1024*1024)) > /sys/block/zram0/disksize
- 也可以使用帶內存單位作為后綴的方式設置內存大小
echo 256K > /sys/block/zram0/disksize
echo 512M > /sys/block/zram0/disksize
echo 1G > /sys/block/zram0/disksize
6. 啟用 zram 設備為 swap
mkswap /dev/zram0
swapon /dev/zram0
7. 具體的 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 | 觸發內存壓縮 |
8. 系統運行之后的內存統計情況
cat /proc/meminfo
1 MemTotal: 1958596 kB
2 MemFree: 40364 kB
3 Buffers: 3472 kB
4 Cached: 328080 kB
5 SwapCached: 1908 kB
6 Active: 906752 kB
7 Inactive: 426648 kB
8 Active(anon): 752824 kB
9 Inactive(anon): 252756 kB
10 Active(file): 153928 kB
11 Inactive(file): 173892 kB
12 Unevictable: 2516 kB
13 Mlocked: 0 kB
14 SwapTotal: 524284 kB
15 SwapFree: 378320 kB
16 Dirty: 480 kB
17 Writeback: 0 kB
18 AnonPages: 1003452 kB
19 Mapped: 167052 kB
20 Shmem: 1184 kB
21 Slab: 83104 kB
22 SReclaimable: 24368 kB
23 SUnreclaim: 58736 kB
24 KernelStack: 48736 kB
25 PageTables: 41908 kB
26 NFS_Unstable: 0 kB
27 Bounce: 0 kB
28 WritebackTmp: 0 kB
29 CommitLimit: 1503580 kB
30 Committed_AS: 94718220 kB
31 VmallocTotal: 251658176 kB
32 VmallocUsed: 181352 kB
33 VmallocChunk: 251373156 kB
從 Line 14,15 可以看到 swap 相關的統計信息,SwapTotal 的大小就是 zram 設備的大小,當系統開啟了一段時間之后,就會將后台的一些優先級低的應用數據(匿名頁面)壓縮存放到 swap 區,然后再重新打開這些應用的時候,再從 swap 區將它們的數據解壓出來。在 Android KitKat 版本之前,Android 設備因為沒有 zram,所以查看 /proc/meinfo 看到的 swap 分區的大小和統計數據都會是零。
1 Total RAM: 1958596 kB (status normal)
2 Free RAM: 724527 kB (504283 cached pss + 183244 cached kernel + 37000 free)
3 Used RAM: 1014008 kB (656204 used pss + 357804 kernel)
4 Lost RAM: 220061 kB
5 ZRAM: 27296 kB physical used for 145952 kB in swap (524284 kB total swap)
6 Tuning: 256 (large 512), oom 286720 kB, restore limit 95573 kB (high-end-gfx)
Line 5 也可以看到 swap 相關的統計信息,如果需要查看具體某個進程使用了多少 swap 空間,可以通過 dumpsys meminfo pid
(該進程的 id 號)查看。
zram 具體原理分析
zram 本質是就是一個塊設備,所以下面先簡單介紹一下塊設備的一些基礎知識。
1. 塊設備基礎概念
-
塊設備(block device)
塊設備是一種具有一定結構的隨機存取設備,對這種設備的讀寫是按塊進行的,使用緩沖區來存放暫時的數據,待條件成熟后,從緩存一次性寫入設備或者從設備一次性讀到緩沖區。
-
扇區 (Sectors)
塊設備中最小的可尋址單元,大小一般都是 2 的整數倍,最常見的是 512 字節。
-
塊 (Blocks)
塊是文件系統的一種抽象,只能基於塊來訪問文件系統,塊必須是扇區大小的 2 的整數倍,並且要小於頁面的大小,所以通常塊的大小是 512 字節、1 KB 或 4 KB 。
-
段 (Segments)
由若干個相鄰的塊組成,是 Linux 內存管理機制中一個內存頁或者內存頁的一部分。
-
頁面 (Page)
物理頁是 Linux 內存管理的基本單位,一般一個頁面是 4KB 或者 64 KB。
2. 塊設備驅動整體框架3
3. 相關數據結構
-
block_device
描述一個分區或整個磁盤對內核的一個塊設備實例。
-
gendisk
描述一個通用硬盤(generic hard disk)對象。
-
hd_struct
描述分區應有的分區信息。
-
bio
描述塊數據傳送時怎樣完成填充或讀取塊給 driver,既描述了磁盤的位置,又描述了內存的位置。
-
bio_vec
描述 bio 中的每個段。
-
request
描述向內核請求一個列表准備做隊列處理。
-
request_queue
描述內核申請 request 資源建立請求鏈表並填寫 bio 形成隊列。
4. zram 架構
zram 從架構上可以分為三部分:
-
驅動部分
該部分創建了一個塊設備,然后提供了處理 IO 請求的接口;
-
數據流操作部分
該部分主要提供串行或者並行的壓縮和解壓操作;
-
解壓縮算法部分
該部分主要是一個個壓縮和解壓算法,每個算法都提供統一的壓縮和解壓接口給數據流操作部分調用。
5. 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 。
6. 數據流操作部分代碼分析
-
zcomp_create
若最大可能同時執行壓縮操作的個數來調用為一,則調用 zcomp_strm_single_create 來創建一個壓縮流,而若最大可能同時執行壓縮操作的個數來調用大於一,則調用 zcomp_strm_multi_create 先創建一個壓縮流,然后創建一個壓縮流鏈表,並將創建好的壓縮流加到壓縮流鏈表中,后面再根據需求來動態創建更多的壓縮流。
-
zcomp_strm_multi_find
單壓縮流非常簡單,如果前一個壓縮操作已經持有 strm_lock 鎖,那么下一個壓縮操作必須等待前一個壓縮操作調用 zcomp_strm_single_release 釋放該鎖才可以接着執行。
-
zcomp_strm_multi_find
多壓縮流就相對復雜一點,只要壓縮流的個數沒有達到最大的個數,那么壓縮操作都可以分配到一個壓縮流,並會加到壓縮流鏈表中,當壓縮流的個數達到最大限制之后,那么下一個壓縮操作只能睡眠等待鏈表中有空閑的壓縮流出現。
參考資料
本作品由 Jacket 創作,並由本站發表。未經授權,禁止轉載。