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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM