Skytoby

fstrim解析

fstrim解析

fstrim提升磁盘性能,缓解Android卡顿

Android手机刚购买时非常流畅,但是使用不到一年之后都会“卡顿”。那么是什么原因造成的呢?

长期使用Android手机将产生大量的磁盘碎片,而磁盘碎片将会降低磁盘的读写性能,从而影响系统流畅度。

1. Android磁盘的读写机制

Android手机大多数采用NAND flash架构的闪存卡来存储内容,NAND Flash的内部存储单位从小到大依次为:Page、Block、Device,下面是一个NAND Flash组成结构的示意图。

虽然NAND Flash的优点很多,但是为了延长驱动器的寿命,它的读写操作是以Page为单位进行的,但擦除操作确实按照Block为单位进行的。

由于有大量的读写操作,于是NAND Flash制定了如下读写规则:

1.删除数据时,芯片将标记这些Page为闲置状态,但并不会立马执行擦除操作

2.写入数据时,如果目前磁盘剩余充足,则由芯片指定Block后直接按Page为单位进行写入操作。

3.写入数据时,如果目前磁盘剩余空间不足,为了获得足够的空间,磁盘先将某块Block的内容读直缓存,然后再在该Block上进行擦除操作,最后将新内容与原先内容一起写入至该Block。

其实想存储的就是1个Page的图片内容,但是实际上确造成了整个Block的内容都被重新写入,同时原本简单一步搞定的事情被还被分成了前后四步执行(闪存读取、缓存改、闪存擦除、闪存写入)造成延迟大大增加,速度变慢。这就是传说中的“写入放大”(Write Amplification)问题。而“写入放大”也说明了磁盘在长期使用的过程中,其读写速度(尤其是写入速度)会存在降低的现象。

2. 解决“写入放大”问题的技术——TRIM

Google的解决方案TRIM技术。

TRIM是一条ATA指令,由操作系统发送给闪存主控制器,告诉它哪些数据占的地址是“无效”的。在TRIM的帮助下,闪存主控制器就可以提前知道哪些Page是“无效”的,便可以在适当的时机做出优化,从而改善性能。这里要强调下,TRIM只是条指令,让操作系统告诉闪存主控制器这个Page已经“无效”就算完了,并没有任何其它多余的操作。TRIM的触发需要操作系统、驱动程序以及闪存主控三者都支持才能真正意义上实现。例如:

操作系统不支持的情况:Android 4.3以下均不支持
闪存主控不支持的情况:Samsung Galaxy Nexus(I9250)所选用的闪存不支持
基于TRIM技术,目前常见有两种方案可以解决“写入放大”的问题:

discard选项。该方案将在挂载 ext4 分区时加上 discard 选项,此后操作系统在执行每一个磁盘操作时同时都会执行 TRIM 指令。该方案的优点是总体耗时短,但会影响到删除文件时的性能。
fstrim命令。该方案将选择合适的时机对整个分区执行TRIM操作。相对于方案一,该方案总体耗时较长,但不会影响正常操作时的磁盘性能。

从用户的角度出发,还是FSTRIM的方法更靠谱一些。

3. TRIM在Android上实现

在PackageManagerServic中有performFstrimIfNeeded方法,最后执行的是StorageManagerService.fstrim

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Override
public void fstrim(int flags, IVoldTaskListener listener) {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);

try {
mVold.fstrim(flags, new IVoldTaskListener.Stub() {
@Override
public void onStatus(int status, PersistableBundle extras) {
dispatchOnStatus(listener, status, extras);

// Ignore trim failures
if (status != 0) return;

final String path = extras.getString("path");
final long bytes = extras.getLong("bytes");
final long time = extras.getLong("time");

final DropBoxManager dropBox = mContext.getSystemService(DropBoxManager.class);
dropBox.addText(TAG_STORAGE_TRIM, scrubPath(path) + " " + bytes + " " + time);

synchronized (mLock) {
final VolumeRecord rec = findRecordForPath(path);
if (rec != null) {
rec.lastTrimMillis = System.currentTimeMillis();
writeSettingsLocked();
}
}
}

@Override
public void onFinished(int status, PersistableBundle extras) {
dispatchOnFinished(listener, status, extras);

// TODO: benchmark when desired
}
});
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}

到这来,调用的是mVold,而mVold初始化如下,那么mVold的服务端在哪呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
binder = ServiceManager.getService("vold");
if (binder != null) {
try {
binder.linkToDeath(new DeathRecipient() {
@Override
public void binderDied() {
Slog.w(TAG, "vold died; reconnecting");
mVold = null;
connect();
}
}, 0);
} catch (RemoteException e) {
binder = null;
}
}

if (binder != null) {
mVold = IVold.Stub.asInterface(binder);
try {
mVold.setListener(mListener);
} catch (RemoteException e) {
mVold = null;
Slog.w(TAG, "vold listener rejected; trying again", e);
}
} else {
Slog.w(TAG, "vold not found; trying again");
}

4. Vold守护进程启动

Vold负责挂载SD卡,vold的全称是volume daemon,实际上负责完成系统的CDROM

USB大容量存储,MMC卡等拓展存储的挂载任务自动完成的守护进程。它提供的主要特点是支持这些存储设备外设的热插拔。

Vold相关的代码在/system/vold目录中

对应的启动在vold.rc中,其内容如下:

1
2
3
4
5
6
7
8
service vold /system/bin/vold \
--blkid_context=u:r:blkid:s0 --blkid_untrusted_context=u:r:blkid_untrusted:s0 \
--fsck_context=u:r:fsck:s0 --fsck_untrusted_context=u:r:fsck_untrusted:s0
class core
ioprio be 2
writepid /dev/cpuset/foreground/tasks
shutdown critical
group reserved_disk

上面定义了一个vold的service,去执行vold程序,对应的入口在vold目录下的main.cpp中。

main.cpp中主要是创建了VolumeManager、NetlinkManager的实例,并且启动了VoldNativeService服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
int main(int argc, char** argv) {
atrace_set_tracing_enabled(false);
setenv("ANDROID_LOG_TAGS", "*:v", 1);
android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM));

LOG(INFO) << "Vold 3.0 (the awakening) firing up";

ATRACE_BEGIN("main");


LOG(VERBOSE) << "Detected support for:"
<< (android::vold::IsFilesystemSupported("ext4") ? " ext4" : "")
<< (android::vold::IsFilesystemSupported("f2fs") ? " f2fs" : "")
<< (android::vold::IsFilesystemSupported("vfat") ? " vfat" : "");

VolumeManager *vm;
NetlinkManager *nm;

parse_args(argc, argv);

sehandle = selinux_android_file_context_handle();
if (sehandle) {
selinux_android_set_sehandle(sehandle);
}

mkdir("/dev/block/vold", 0755);

/* For when cryptfs checks and mounts an encrypted filesystem */
klog_set_level(6);

/* Create our singleton managers */
if (!(vm = VolumeManager::Instance())) {
LOG(ERROR) << "Unable to create VolumeManager";
exit(1);
}

if (!(nm = NetlinkManager::Instance())) {
LOG(ERROR) << "Unable to create NetlinkManager";
exit(1);
}

if (android::base::GetBoolProperty("vold.debug", false)) {
vm->setDebug(true);
}

if (vm->start()) {
PLOG(ERROR) << "Unable to start VolumeManager";
exit(1);
}

bool has_adoptable;
bool has_quota;
bool has_reserved;

if (process_config(vm, &has_adoptable, &has_quota, &has_reserved)) {
PLOG(ERROR) << "Error reading configuration... continuing anyways";
}

ATRACE_BEGIN("VoldNativeService::start");
if (android::vold::VoldNativeService::start() != android::OK) {
LOG(ERROR) << "Unable to start VoldNativeService";
exit(1);
}
ATRACE_END();

LOG(DEBUG) << "VoldNativeService::start() completed OK";

ATRACE_BEGIN("NetlinkManager::start");
if (nm->start()) {
PLOG(ERROR) << "Unable to start NetlinkManager";
exit(1);
}
ATRACE_END();

// This call should go after listeners are started to avoid
// a deadlock between vold and init (see b/34278978 for details)
android::base::SetProperty("vold.has_adoptable", has_adoptable ? "1" : "0");
android::base::SetProperty("vold.has_quota", has_quota ? "1" : "0");
android::base::SetProperty("vold.has_reserved", has_reserved ? "1" : "0");

// Do coldboot here so it won't block booting,
// also the cold boot is needed in case we have flash drive
// connected before Vold launched
coldboot("/sys/block");

ATRACE_END();

android::IPCThreadState::self()->joinThreadPool();
LOG(INFO) << "vold shutting down";

exit(0);
}

上面mVold.fstrim方法是通过binder远程调用了VoldNativeService::fstrim方法

1
2
3
4
5
6
7
8
9
10
binder::Status VoldNativeService::fstrim(int32_t fstrimFlags,
const android::sp<android::os::IVoldTaskListener>& listener) {
ENFORCE_UID(AID_SYSTEM);
ACQUIRE_LOCK;

std::thread([=]() {
android::vold::Trim(listener);
}).detach();
return ok();
}

android::vold::Trim方法在IdleMaint.cpp中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
void Trim(const android::sp<android::os::IVoldTaskListener>& listener) {
acquire_wake_lock(PARTIAL_WAKE_LOCK, kWakeLock);

// Collect both fstab and vold volumes
std::list<std::string> paths;
addFromFstab(&paths, PathTypes::kMountPoint);
addFromVolumeManager(&paths, PathTypes::kMountPoint);

for (const auto& path : paths) {
LOG(DEBUG) << "Starting trim of " << path;

android::os::PersistableBundle extras;
extras.putString(String16("path"), String16(path.c_str()));

int fd = open(path.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
if (fd < 0) {
PLOG(WARNING) << "Failed to open " << path;
if (listener) {
listener->onStatus(-1, extras);
}
continue;
}

struct fstrim_range range;
memset(&range, 0, sizeof(range));
range.len = ULLONG_MAX;

nsecs_t start = systemTime(SYSTEM_TIME_BOOTTIME);
if (ioctl(fd, FITRIM, &range)) {
PLOG(WARNING) << "Trim failed on " << path;
if (listener) {
listener->onStatus(-1, extras);
}
} else {
nsecs_t time = systemTime(SYSTEM_TIME_BOOTTIME) - start;
LOG(INFO) << "Trimmed " << range.len << " bytes on " << path
<< " in " << nanoseconds_to_milliseconds(time) << "ms";
extras.putLong(String16("bytes"), range.len);
extras.putLong(String16("time"), time);
if (listener) {
listener->onStatus(0, extras);
}
}
close(fd);
}

if (listener) {
android::os::PersistableBundle extras;
listener->onFinished(0, extras);
}

release_wake_lock(kWakeLock);
}