zram writeback
概述
zram-writeback就是像电脑的swap分区那样,将zram中的压缩数据,写回到磁盘当中
执行流程
开机的时候:StorageManagerService: handleSystemReady
-> if (!zramPropValue.equals("0") // persist.sys.zram_enabled属性设为1
&& mContext.getResources().getBoolean(
com.android.internal.R.bool.config_zramWriteback)) {// overlay配置<bool name="config_zramWriteback">true</bool>
ZramWriteback.scheduleZramWriteback(mContext);// schedule运行
}
-> public static void scheduleZramWriteback(Context context) {
int markIdleDelay = SystemProperties.getInt(MARK_IDLE_DELAY_PROP, 20); // ro.zram.mark_idle_delay_mins
int firstWbDelay = SystemProperties.getInt(FIRST_WB_DELAY_PROP, 180); // ro.zram.first_wb_delay_mins
boolean forceWb = SystemProperties.getBoolean(FORCE_WRITEBACK_PROP, false); // zram.force_writeback
JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
// Schedule a one time job to mark pages as idle. These pages will be written
// back at later point if they remain untouched.
js.schedule(new JobInfo.Builder(MARK_IDLE_JOB_ID, sZramWriteback)
.setMinimumLatency(TimeUnit.MINUTES.toMillis(markIdleDelay))
.setOverrideDeadline(TimeUnit.MINUTES.toMillis(markIdleDelay))
.build());
// Schedule a one time job to flush idle pages to disk.
// After the initial writeback, subsequent writebacks are done at interval set
// by ro.zram.periodic_wb_delay_hours.
js.schedule(new JobInfo.Builder(WRITEBACK_IDLE_JOB_ID, sZramWriteback)
.setMinimumLatency(TimeUnit.MINUTES.toMillis(firstWbDelay))
.setRequiresDeviceIdle(!forceWb)
.build());
}
源码解析
开机framework流程
1. StorageManagerService类
frameworks/base/services/core/java/com/android/server/StorageManagerService.java
1.1 handleSystemReady
private void handleSystemReady() {
// Start scheduling nominally-daily fstrim operations
MountServiceIdler.scheduleIdlePass(mContext);
// Toggle zram-enable system property in response to settings
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.ZRAM_ENABLED),
false /*notifyForDescendants*/,
new ContentObserver(null /* current thread */) {
@Override
public void onChange(boolean selfChange) {
refreshZramSettings();
}
});
refreshZramSettings();
// Schedule zram writeback unless zram is disabled by persist.sys.zram_enabled
String zramPropValue = SystemProperties.get(ZRAM_ENABLED_PROPERTY);
if (!zramPropValue.equals("0")
&& mContext.getResources().getBoolean(
com.android.internal.R.bool.config_zramWriteback)) {
ZramWriteback.scheduleZramWriteback(mContext); // 这里
}
// Toggle isolated-enable system property in response to settings
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.ISOLATED_STORAGE_REMOTE),
false /*notifyForDescendants*/,
new ContentObserver(null /* current thread */) {
@Override
public void onChange(boolean selfChange) {
refreshIsolatedStorageSettings();
}
});
// For now, simply clone property when it changes
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
mContext.getMainExecutor(), (properties) -> {
refreshIsolatedStorageSettings();
refreshFuseSettings();
});
refreshIsolatedStorageSettings();
}
1.2 refreshZramSettings
private void refreshZramSettings() {
String propertyValue = SystemProperties.get(ZRAM_ENABLED_PROPERTY); // "persist.sys.zram_enabled";
if ("".equals(propertyValue)) {
return; // System doesn't have zram toggling support
}
String desiredPropertyValue =// 不定义的话,默认是返回1的;所以只需要配置persist.sys.zram_enabled属性就好了
Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.ZRAM_ENABLED,
1) != 0
? "1" : "0";
if (!desiredPropertyValue.equals(propertyValue)) {
// Avoid redundant disk writes by setting only if we're
// changing the property value. There's no race: we're the
// sole writer.
SystemProperties.set(ZRAM_ENABLED_PROPERTY, desiredPropertyValue);
// Schedule writeback only if zram is being enabled.
if (desiredPropertyValue.equals("1")
&& mContext.getResources().getBoolean(
com.android.internal.R.bool.config_zramWriteback)) {
ZramWriteback.scheduleZramWriteback(mContext);// 这里
}
}
}
2. ZramWriteback类
2.1 scheduleZramWriteback
public static void scheduleZramWriteback(Context context) {
int markIdleDelay = SystemProperties.getInt(MARK_IDLE_DELAY_PROP, 20);
int firstWbDelay = SystemProperties.getInt(FIRST_WB_DELAY_PROP, 180);
boolean forceWb = SystemProperties.getBoolean(FORCE_WRITEBACK_PROP, false);
// jobscheduler服务
JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
// Schedule a one time job to mark pages as idle. These pages will be written
// back at later point if they remain untouched.
js.schedule(new JobInfo.Builder(MARK_IDLE_JOB_ID, sZramWriteback)
.setMinimumLatency(TimeUnit.MINUTES.toMillis(markIdleDelay))
.setOverrideDeadline(TimeUnit.MINUTES.toMillis(markIdleDelay))
.build());
// Schedule a one time job to flush idle pages to disk.
// After the initial writeback, subsequent writebacks are done at interval set
// by ro.zram.periodic_wb_delay_hours.
js.schedule(new JobInfo.Builder(WRITEBACK_IDLE_JOB_ID, sZramWriteback)
.setMinimumLatency(TimeUnit.MINUTES.toMillis(firstWbDelay))
.setRequiresDeviceIdle(!forceWb)
.build());
}
2.2 onStartJob-执行任务
// write /sys/block/zram0/idle "all"
// write /sys/block/zram0/writeback "idle"
@Override
public boolean onStartJob(JobParameters params) {
if (!isWritebackEnabled()) {
jobFinished(params, false);
return false;
}
if (params.getJobId() == MARK_IDLE_JOB_ID) {
markPagesAsIdle();
jobFinished(params, false);
return false;
} else {
new Thread("ZramWriteback_WritebackIdlePages") {
@Override
public void run() {
markAndFlushPages();
schedNextWriteback(ZramWriteback.this);
jobFinished(params, false);
}
}.start();
}
return true;
}
@Override
public boolean onStopJob(JobParameters params) {
// The thread that triggers the writeback is non-interruptible
return false;
}
swapon的流程
1. builtins模块
swapon_all /vendor/etc/fstab.wswap
/dev/block/zram0 none swap defaults zramsize=90%,zram_backingdev_size=256M
1.1 do_swapon_all
static Result<void> do_swapon_all(const BuiltinArguments& args) {
auto swapon_all = ParseSwaponAll(args.args);// /vendor/etc/fstab.wswap
if (!swapon_all.ok()) return swapon_all.error();
Fstab fstab;
if (swapon_all->empty()) {
if (!ReadDefaultFstab(&fstab)) {
return Error() << "Could not read default fstab";
}
} else {
if (!ReadFstabFromFile(*swapon_all, &fstab)) {// entry->zram_size和entry->zram_backingdev_size
return Error() << "Could not read fstab '" << *swapon_all << "'";
}
}
if (!fs_mgr_swapon_all(fstab)) {
return Error() << "fs_mgr_swapon_all() failed";
}
return {};
}
1.2 fs_mgr_swapon_all
bool fs_mgr_swapon_all(const Fstab& fstab) {
bool ret = true;
for (const auto& entry : fstab) {
// Skip non-swap entries.
if (entry.fs_type != "swap") {
continue;
}
if (entry.zram_size > 0) {
if (!PrepareZramBackingDevice(entry.zram_backingdev_size)) {
LERROR << "Failure of zram backing device file for '" << entry.blk_device << "'";
}
// A zram_size was specified, so we need to configure the
// device. There is no point in having multiple zram devices
// on a system (all the memory comes from the same pool) so
// we can assume the device number is 0.
if (entry.max_comp_streams >= 0) {
auto zram_mcs_fp = std::unique_ptr<FILE, decltype(&fclose)>{
fopen(ZRAM_CONF_MCS, "re"), fclose};
if (zram_mcs_fp == nullptr) {
LERROR << "Unable to open zram conf comp device " << ZRAM_CONF_MCS;
ret = false;
continue;
}
fprintf(zram_mcs_fp.get(), "%d\n", entry.max_comp_streams);
}
// 1. 将zram_size的大小写到/sys/block/zram0/disksize文件
auto zram_fp =
std::unique_ptr<FILE, decltype(&fclose)>{fopen(ZRAM_CONF_DEV, "re+"), fclose};
if (zram_fp == nullptr) {
LERROR << "Unable to open zram conf device " << ZRAM_CONF_DEV;
ret = false;
continue;
}
fprintf(zram_fp.get(), "%" PRId64 "\n", entry.zram_size);
}
if (entry.fs_mgr_flags.wait && !WaitForFile(entry.blk_device, 20s)) {
LERROR << "Skipping mkswap for '" << entry.blk_device << "'";
ret = false;
continue;
}
// Initialize the swap area.
const char* mkswap_argv[2] = {
MKSWAP_BIN,
entry.blk_device.c_str(),
};// 2. 执行/system/bin/mkswap /dev/block/zram0命令
int err = logwrap_fork_execvp(ARRAY_SIZE(mkswap_argv), mkswap_argv, nullptr, false,
LOG_KLOG, false, nullptr);
if (err) {
LERROR << "mkswap failed for " << entry.blk_device;
ret = false;
continue;
}
/* If -1, then no priority was specified in fstab, so don't set
* SWAP_FLAG_PREFER or encode the priority */
int flags = 0;
if (entry.swap_prio >= 0) {
flags = (entry.swap_prio << SWAP_FLAG_PRIO_SHIFT) & SWAP_FLAG_PRIO_MASK;
flags |= SWAP_FLAG_PREFER;
} else {
flags = 0;
}// 3. 执行swapon系统调用
err = swapon(entry.blk_device.c_str(), flags);
if (err) {
LERROR << "swapon failed for " << entry.blk_device;
ret = false;
}
}
return ret;
}
1.3 PrepareZramBackingDevice
// 1. 创建size大小的/data/per_boot/zram_swap
// 2. 创建loop设备,directIO的形式和/data/per_boot/zram_swap文件连接起来
// 3. 将loop设备的路径写到/sys/block/zram0/backing_dev节点中
static bool PrepareZramBackingDevice(off64_t size) {
constexpr const char* file_path = "/data/per_boot/zram_swap";
if (size == 0) return true;
// Prepare target path
unique_fd target_fd(TEMP_FAILURE_RETRY(open(file_path, O_RDWR | O_CREAT | O_CLOEXEC, 0600)));
if (target_fd.get() == -1) {
PERROR << "Cannot open target path: " << file_path;
return false;
}
if (fallocate(target_fd.get(), 0, 0, size) < 0) {
PERROR << "Cannot truncate target path: " << file_path;
return false;
}
// Allocate loop device and attach it to file_path.
LoopControl loop_control;
std::string device;
if (!loop_control.Attach(target_fd.get(), 5s, &device)) {
return false;
}
// set block size & direct IO
unique_fd device_fd(TEMP_FAILURE_RETRY(open(device.c_str(), O_RDWR | O_CLOEXEC)));
if (device_fd.get() == -1) {
PERROR << "Cannot open " << device;
return false;
}
if (!LoopControl::EnableDirectIo(device_fd.get())) {
return false;
}
return InstallZramDevice(device);
}
内核模块
1. TODO:内核zram驱动解析
问题
开机之后重启超时
在开完机之后,writeback一次页面
-on property:debug.launcher3.exit=1 && property:ro.vendor.ramconfig=1
- write /sys/block/zram0/idle "all"
- write /sys/block/zram0/writeback "idle"
在init进程中执行write操作,会使init进程卡住,导致重启超时
补充
1. SettingsProvider
android/frameworks/base/packages/SettingsProvider
SettingsProvider的数据保存在文件/data/system/users/0/settings_***.xml
命令查询:
dumpsys settings
settings get
dumpsys content
代码中查询:
Settings.Global.getInt(mResolver, Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY, -1);
mResolver是contentresolver用来查询settingsprovider的,从settingsprovider中查询Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY的值,如果Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY没有定义,则返回最后一个参数值-1
JobScheduler的使用
JobScheduler:允许开发者创建在后台执行的job,当预置的条件被满足时,这些Job将会在后台被执行。在未来的某个时间点或者未来满足某种条件(比如插入电源或者连接WiFi)的情况下下去执行一些操作。主要是为了省电操作。
使用步骤:
(1)创建JobService类;继承JobService类,实现onStartJob和onStopJob接口函数
public final class ZramWriteback extends JobService {
@Override// JobService运行在主线程 需要另外开启线程做耗时工作;可以使用Hanlder,需要把params传过去
public boolean onStartJob(JobParameters params) {
if (!isWritebackEnabled()) {
jobFinished(params, false);
return false;
}
if (params.getJobId() == MARK_IDLE_JOB_ID) {
markPagesAsIdle();
jobFinished(params, false);
return false;
} else {
new Thread("ZramWriteback_WritebackIdlePages") {
@Override
public void run() {
markAndFlushPages();
schedNextWriteback(ZramWriteback.this);
jobFinished(params, false);
}
}.start();
}
// 返回false说明job已经完成 不是个耗时的任务
// 返回true说明job在异步执行 需要手动调用jobFinished告诉系统job完成
// 这里我们返回了true,因为我们要做耗时操作。
// 返回true意味着耗时操作花费的事件比onStartJob执行的事件更长
// 并且意味着我们会手动的调用jobFinished方法
return true;
}
@Override
public boolean onStopJob(JobParameters params) {
// The thread that triggers the writeback is non-interruptible
// 当系统收到一个cancel job的请求时,并且这个job仍然在执行(onStartJob返回true),系统就会调用onStopJob方法。
// 但不管是否调用onStopJob,系统只要收到取消请求,都会取消该job
// true 需要重试
// false 不再重试 丢弃job
return false;
}
}
然后在manifest文件中给service添加一个权限:frameworks/base/core/res/./AndroidManifest.xml文件,framework-res.apk的包名是android
<service android:name="com.android.server.ZramWriteback"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
(2)创建JobInfo,通过builder设定Job的执行选项
//Builder构造方法接收两个参数,第一个参数是jobId,每个app或者说uid下不同的Job,它的jobId必须是不同的
//第二个参数是我们自定义的JobService,系统会回调我们自定义的JobService中的onStartJob和onStopJob方法
new JobInfo.Builder(MARK_IDLE_JOB_ID, sZramWriteback)
.setMinimumLatency(TimeUnit.MINUTES.toMillis(markIdleDelay)) // 20分钟后执行
.setOverrideDeadline(TimeUnit.MINUTES.toMillis(markIdleDelay))// 最晚20分钟执行
.build()
(3)获取JobScheduler服务执行任务
JobScheduler mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
mJobScheduler.schedule(builder.build());
参考
1. [047][译]zram.txt
https://cloud.tencent.com/developer/article/1639814
2. Android系统APP之SettingsProvider
https://www.jianshu.com/p/d48977f220ee
3. [Android] 后台任务系列之JobScheduler
https://cloud.tencent.com/developer/article/1578466