Create a ZRAM swap device
Implement simple mkswap/swapon functionality within microdroid
manager. Use it to configure a ZRAM swap device.
Bug: 238284600
Test: Start a VM, confirm swap is available
Change-Id: I8757d1f9118c1fc14d4e8c9580e37d60d699d264
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index a6856d0..78f8bb4 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -18,6 +18,7 @@
mod instance;
mod ioutil;
mod payload;
+mod swap;
mod vm_payload_service;
use crate::dice::{DiceContext, DiceDriver};
@@ -170,6 +171,9 @@
load_crashkernel_if_supported().context("Failed to load crashkernel")?;
+ swap::init_swap().context("Failed to initialise swap")?;
+ info!("swap enabled.");
+
let service = get_vms_rpc_binder()
.context("cannot connect to VirtualMachineService")
.map_err(|e| MicrodroidError::FailedToConnectToVirtualizationService(e.to_string()))?;
diff --git a/microdroid_manager/src/swap.rs b/microdroid_manager/src/swap.rs
new file mode 100644
index 0000000..0fd1468
--- /dev/null
+++ b/microdroid_manager/src/swap.rs
@@ -0,0 +1,102 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Logic for configuring and enabling a ZRAM-backed swap device.
+
+use anyhow::{anyhow, Context, Result};
+use std::fs::{read_to_string, OpenOptions};
+use std::io::{Error, Seek, SeekFrom, Write};
+use uuid::Uuid;
+
+const SWAP_DEV: &str = "block/zram0";
+
+/// Parse "MemTotal: N kB" from /proc/meminfo
+fn get_total_memory_kb() -> Result<u32> {
+ let s = read_to_string("/proc/meminfo")?;
+ let mut iter = s.split_whitespace();
+ while let Some(x) = iter.next() {
+ if x.starts_with("MemTotal:") {
+ let n = iter.next().context("No text after MemTotal")?;
+ return n.parse::<u32>().context("No u32 after MemTotal");
+ }
+ }
+ Err(anyhow!("MemTotal not found in /proc/meminfo"))
+}
+
+/// Simple "mkswap": Writes swap-device header into specified device.
+/// The header has no formal public definition, but it can be found in the
+/// Linux source tree at include/linux/swap.h (union swap_header).
+/// This implementation is inspired by the one in Toybox.
+fn mkswap(dev: &str) -> Result<()> {
+ // Size of device, in bytes.
+ let sysfs_size = format!("/sys/{}/size", dev);
+ let len = read_to_string(&sysfs_size)?
+ .trim()
+ .parse::<u32>()
+ .context(format!("No u32 in {}", &sysfs_size))?
+ * 512;
+
+ let pagesize: libc::c_uint;
+ // safe because we give a constant and known-valid sysconf parameter
+ unsafe {
+ pagesize = libc::sysconf(libc::_SC_PAGE_SIZE) as libc::c_uint;
+ }
+
+ let mut f = OpenOptions::new().read(false).write(true).open(format!("/dev/{}", dev))?;
+
+ // Write the info fields: [ version, last_page ]
+ let info: [u32; 2] = [1, (len / pagesize) - 1];
+ f.seek(SeekFrom::Start(1024))?;
+ f.write_all(&info.iter().flat_map(|v| v.to_ne_bytes()).collect::<Vec<u8>>())?;
+
+ // Write a random version 4 UUID
+ f.seek(SeekFrom::Start(1024 + 12))?;
+ f.write_all(Uuid::new_v4().as_bytes())?;
+
+ // Write the magic signature string.
+ f.seek(SeekFrom::Start((pagesize - 10) as u64))?;
+ f.write_all("SWAPSPACE2".as_bytes())?;
+
+ Ok(())
+}
+
+/// Simple "swapon", using libc:: wrapper.
+fn swapon(dev: &str) -> Result<()> {
+ let swapon_arg = std::ffi::CString::new(format!("/dev/{}", dev))?;
+ // safe because we give a nul-terminated string and check the result
+ let res = unsafe { libc::swapon(swapon_arg.as_ptr(), 0) };
+ if res != 0 {
+ return Err(anyhow!("Failed to swapon: {}", Error::last_os_error()));
+ }
+ Ok(())
+}
+
+/// Turn on ZRAM-backed swap
+pub fn init_swap() -> Result<()> {
+ let dev = SWAP_DEV;
+
+ // Create a ZRAM block device the same size as total VM memory.
+ let mem_kb = get_total_memory_kb()?;
+ OpenOptions::new()
+ .read(false)
+ .write(true)
+ .open(format!("/sys/{}/disksize", dev))?
+ .write_all(format!("{}K", mem_kb).as_bytes())?;
+
+ mkswap(dev)?;
+
+ swapon(dev)?;
+
+ Ok(())
+}