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
posted @ 2021-07-26 21:01  pyjetson  阅读(2207)  评论(0编辑  收藏  举报