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: