emmc FITRIM/discard命令在android系统中提升write performance的运用

SSD/emmc 存储设备discard命令的使用意义及使用方法,请参考xfs官网上的这篇文章,描述详细。 

http://xfs.org/index.php/FITRIM/discard


FITRIM/discard我的理解是:discard命令,只是将page标识为dirty状态,并未做真正的block erase操作。

而当一个block大部份page都标识为dirty时,为重新利用该block,就得将数据有效page(none dirty page)迁移到别的block,同时整个block擦除掉。

这个过程我们称之为:垃圾页回收。============》FITRIM就是做此工作的。

为什么它能提升emmc write performance 呢?

因为nand读、写规则是: 以page为读、写单元,而以block作为擦除单元。 写一个已经写过的page之前,必须将该页所在的block先擦除。

那么如果能在系统空闲时将一些垃圾页提前回收,并将block预先擦除作为写的准备。当然可以提升write performance,同时也可以提高page利用率。


下面我重点介绍disdcard在android系统中的实时使用和批量使用方式:

Realtime discard

 init.rc 可以看到mount时,就带了disdcard flag ,这样kernel emmc /block 就会做很多不同的处理。
注:kernel需3.0以后版本。

mount ext4 /emmc@usrdata /data noatime nosuid nodev wait noauto_da_alloc,discard

Batch Mode


android4.4 在系统满足如下条件的情况,通过FSTRIM ioctl的方式对device进行垃圾回收的处理,条件如下: 
1)设备已经闲置了至少一小时; 
2)在过去24小时内没有进行清理回收工作; 
3)电池电量大于 30%(充电中)或者80%(未进行充电)。 

具体代码分析如下;

IdleMaintenanceService.java

    private void updateIdleMaintenanceState(boolean noisy) {
        if (mIdleMaintenanceStarted) {
            // Idle maintenance can be interrupted by user activity, or duration
            // time out, or low battery.
            if (!lastUserActivityPermitsIdleMaintenanceRunning()
                    || !batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()) {
                unscheduleUpdateIdleMaintenanceState();
                mIdleMaintenanceStarted = false;
                EventLogTags.writeIdleMaintenanceWindowFinish(SystemClock.elapsedRealtime(),
                        mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
                        isBatteryCharging() ? 1 : 0);
                sendIdleMaintenanceEndIntent();
                // We stopped since we don't have enough battery or timed out but the
                // user is not using the device, so we should be able to run maintenance
                // in the next maintenance window since the battery may be charged
                // without interaction and the min interval between maintenances passed.
                if (!batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()) {
                    scheduleUpdateIdleMaintenanceState(
                            getNextIdleMaintenanceIntervalStartFromNow());
                }
            }
        } else if (deviceStatePermitsIdleMaintenanceStart(noisy)         //  FITRIM 执行条件判断          
                && lastUserActivityPermitsIdleMaintenanceStart(noisy)
                && lastRunPermitsIdleMaintenanceStart(noisy)) {
            // Now that we started idle maintenance, we should schedule another
            // update for the moment when the idle maintenance times out.
            scheduleUpdateIdleMaintenanceState(MAX_IDLE_MAINTENANCE_DURATION);
            mIdleMaintenanceStarted = true;
            EventLogTags.writeIdleMaintenanceWindowStart(SystemClock.elapsedRealtime(),
                    mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
                    isBatteryCharging() ? 1 : 0);
            mLastIdleMaintenanceStartTimeMillis = SystemClock.elapsedRealtime();
            sendIdleMaintenanceStartIntent();                  // 条件满足 发送广播给mountservice.java   
        } else if (lastUserActivityPermitsIdleMaintenanceStart(noisy)) {
             if (lastRunPermitsIdleMaintenanceStart(noisy)) {
                // The user does not use the device and we did not run maintenance in more
                // than the min interval between runs, so schedule an update - maybe the
                // battery will be charged latter.
                scheduleUpdateIdleMaintenanceState(MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START);
             } else {
                 // The user does not use the device but we have run maintenance in the min
                 // interval between runs, so schedule an update after the min interval ends.
                 scheduleUpdateIdleMaintenanceState(
                         getNextIdleMaintenanceIntervalStartFromNow());
             }
        }
    }


MountService.java

    private final BroadcastReceiver mIdleMaintenanceReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            waitForReady();
            String action = intent.getAction();
            // Since fstrim will be run on a daily basis we do not expect
            // fstrim to be too long, so it is not interruptible. We will
            // implement interruption only in case we see issues.
            if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action)) {
                try {
                    // This method runs on the handler thread,
                    // so it is safe to directly call into vold.
                    mConnector.execute("fstrim", "dotrim");                   //  发送fstrim命令
                    EventLogTags.writeFstrimStart(SystemClock.elapsedRealtime());
                } catch (NativeDaemonConnectorException ndce) {
                    Slog.e(TAG, "Failed to run fstrim!");
                }
            }
        }
    };



CommandListener.cpp

CommandListener::FstrimCmd::FstrimCmd() :
                 VoldCommand("fstrim") {
}
int CommandListener::FstrimCmd::runCommand(SocketClient *cli,
                                                      int argc, char **argv) {
。。。。。。。

    if (!strcmp(argv[1], "dotrim")) {

        dumpArgs(argc, argv, -1);
        rc = fstrim_filesystems();                                        //接收到fstrim命令后,调用fstrim.c 中的fstrim_filesystems()

    } else {
        dumpArgs(argc, argv, -1);
        cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown fstrim cmd", false);
    }



fstrim.c

static void *do_fstrim_filesystems(void *ignored)
{
    int i;
    int fd;
    int ret = 0;
    struct fstrim_range range = { 0 };
    struct stat sb;
    extern struct fstab *fstab;
    SLOGI("Starting fstrim work...\n");
    /* Log the start time in the event log */
    LOG_EVENT_LONG(LOG_FSTRIM_START, get_boot_time_ms());

    for (i = 0; i < fstab->num_entries; i++) {
 。。。。。。
        fd = open(fstab->recs[i].mount_point, O_RDONLY);
        if (fd < 0) {
            SLOGE("Cannot open %s for FITRIM\n", fstab->recs[i].mount_point);
            ret = -1;
            continue;
        }

        memset(&range, 0, sizeof(range));
        range.len = ULLONG_MAX;         // 指定fitrim的范围是整个emmc
        SLOGI("Invoking FITRIM ioctl on %s", fstab->recs[i].mount_point);
        if (ioctl(fd, FITRIM, &range)) {          // 调用特定文件系统中的FITRIM ioctl,执行真正的垃圾回收处理。

  。。。。。。
        close(fd);
    }

 。。。。。。。。。。。。。
}


好的,看到这里,垃圾页回收整个过程基本清楚了。

我们可以看到fstrim是需要文件系统支持的,比如,kernel 3.4版本只有如下几种文件系统支持fstrim。!


ioctl.c (\android\kernel\fs\btrfs): case FITRIM:
ioctl.c (\android\kernel\fs\ext3): case FITRIM: {
ioctl.c (\android\kernel\fs\ext4): case FITRIM:
ioctl.c (\android\kernel\fs\ext4): "FITRIM not supported with bigalloc");
ioctl.c (\android\kernel\fs\ext4): case FITRIM:
ioctl.c (\android\kernel\fs\ocfs2): case FITRIM:
ioctl.c (\android\kernel\fs\ocfs2): case FITRIM:
xfs_ioctl.c (\android\kernel\fs\xfs): case FITRIM:



你可能感兴趣的:(emmc FITRIM/discard命令在android系统中提升write performance的运用)