Android 15存储子系统深度解析(三):FBE加密文件系统与存储性能优化实战

342 阅读24分钟

引言

在前两篇文章中,我们深入分析了Android 15的Vold存储管理框架和FUSE文件系统。本篇作为存储子系统系列的收官之作,将重点聚焦于存储安全性能优化两大核心主题:

  • FBE(File-Based Encryption):Android 7.0引入、在Android 15进一步增强的文件级加密机制
  • f2fs:针对Flash存储优化的文件系统,Android 15默认推荐文件系统
  • 存储性能优化:从诊断到调优的完整实战方法论

这三者紧密关联:FBE加密保证了数据安全但会带来性能开销,f2fs通过专门优化减少这一开销,而性能优化则需要理解二者的配合机制。

本文内容概览

  1. FBE加密机制深度解析

    • FBE vs FDE对比
    • CE/DE密钥体系
    • 加密策略与密钥派生
    • Keymaster HAL与硬件支持
  2. f2fs文件系统核心特性

    • Flash友好的设计哲学
    • 多头日志与热/冷数据分离
    • 在线碎片整理与GC机制
    • Android 15中的f2fs增强
  3. Metadata加密与完整性保护

    • dm-default-key设备映射器
    • Metadata加密实现
    • fsverity完整性验证
  4. 存储性能分析与优化

    • I/O性能基准测试
    • systrace/perfetto存储追踪
    • 常见性能问题诊断
    • f2fs调优参数详解
  5. 实战:存储问题诊断案例

    • 慢速读写问题定位
    • 随机I/O性能调优
    • 空间占用异常排查

让我们开始深入探索Android 15存储系统的安全与性能奥秘!


一、FBE加密机制深度解析

1.1 为什么需要FBE?

在Android 7.0之前,Android使用FDE(Full Disk Encryption,全盘加密):整个/data分区使用单一密钥加密,解锁手机后密钥加载到内存,所有数据可访问。

FDE的问题

  • 开机后无法接听电话、接收闹钟(因为/data未解密)
  • 必须先输入密码才能启动系统核心功能
  • OTA升级需要用户手动输入密码

FBE的优势

┌─────────────────┬──────────────────┐
│  FDE (全盘加密)  │   FBE (文件加密)   │
├─────────────────┼──────────────────┤
│ 单一加密密钥      │  多密钥体系        │
│ 必须解锁才能开机  │  Direct Boot支持  │
│ 无法接听来电      │  开机即可接电话    │
│ OTA需要密码      │  OTA可后台进行    │
│ 性能开销大       │  按需加密,开销小  │
└─────────────────┴──────────────────┘

1.2 FBE核心概念:CE与DE密钥

FBE引入了两级密钥体系

06-01-fbe-encryption-architecture.png

DE (Device Encrypted) - 设备加密

// system/vold/FsCrypt.cpp - DE密钥路径
static std::string get_de_key_path(userid_t user_id) {
    return StringPrintf("%s/de/%d", user_key_dir.c_str(), user_id);
}

特点

  • 开机后即可用(系统启动时自动加载)
  • 不依赖用户凭证(PIN/密码/指纹)
  • 用于存储系统核心功能数据

典型用途

# DE加密的目录示例
/data/user_de/0/com.android.providers.telephony  # 电话应用
/data/user_de/0/com.android.deskclock           # 闹钟应用
/data/user_de/0/com.android.bluetooth           # 蓝牙

CE (Credential Encrypted) - 凭证加密

// system/vold/FsCrypt.cpp - CE密钥路径
static std::string get_ce_key_directory_path(userid_t user_id) {
    return StringPrintf("%s/ce/%d", user_key_dir.c_str(), user_id);
}

特点

  • 用户解锁后才可用(从Keymaster派生)
  • 基于用户凭证(PIN/密码/指纹)
  • 用户锁屏后,CE密钥从内存清除

典型用途

# CE加密的目录示例
/data/user/0/com.android.providers.contacts     # 联系人
/data/user/0/com.android.providers.media       # 相册
/data/user/0/com.whatsapp                      # 应用私有数据

1.3 FBE密钥派生流程

让我们从源码角度看密钥是如何生成和管理的:

1.3.1 DE密钥创建

// system/vold/FsCrypt.cpp
static bool create_de_key(userid_t user_id, bool ephemeral) {
    KeyBuffer de_key;
    std::string de_key_path = get_de_key_path(user_id);

    // 1. 生成随机密钥(或从Keymaster派生)
    auto const& options = BuildDataEncryptionOptions(s_data_options, ephemeral);
    KeyGeneration key_gen = makeGen(options);

    if (!generateStorageKey(key_gen, &de_key)) {
        LOG(ERROR) << "Failed to generate DE key for user " << user_id;
        return false;
    }

    // 2. 将密钥存储到文件系统
    if (!android::vold::storeKey(de_key_path, user_key_temp, de_key)) {
        LOG(ERROR) << "Failed to store DE key";
        return false;
    }

    // 3. 在内核中安装加密策略
    EncryptionPolicy de_policy;
    if (!installKey(de_key, de_policy)) {
        LOG(ERROR) << "Failed to install DE policy";
        return false;
    }

    s_de_policies[user_id].internal = de_policy;
    LOG(INFO) << "Created DE key for user " << user_id;
    return true;
}

关键步骤

  1. 生成密钥:调用generateStorageKey()生成AES-256密钥
  2. 持久化存储:密钥文件存放在/data/misc/vold/user_keys/de/<userid>/
  3. 内核安装:通过FS_IOC_ADD_ENCRYPTION_KEY ioctl安装到内核

1.3.2 CE密钥创建与派生

// system/vold/FsCrypt.cpp
bool fscrypt_prepare_user_storage(const std::string& volume_uuid,
                                   userid_t user_id,
                                   int serial,
                                   int flags) {
    // CE密钥创建
    if (flags & android::os::IVold::STORAGE_FLAG_CE) {
        // 1. 从用户凭证派生密钥
        android::vold::KeyAuthentication auth;
        if (!getUserKeyAuth(user_id, &auth)) {
            return false;
        }

        KeyBuffer ce_key;
        if (!retrieveOrGenerateKey(ce_key_path, auth, makeGen(s_data_options),
                                   &ce_key)) {
            return false;
        }

        // 2. 安装CE加密策略
        EncryptionPolicy ce_policy;
        if (!install_storage_key(BuildDataPath(volume_uuid),
                                s_data_options, ce_key, &ce_policy)) {
            return false;
        }

        s_ce_policies[user_id].internal = ce_policy;
    }

    return true;
}

CE密钥派生链

用户密码/PIN
    ↓
scrypt(密码, salt)  ← 密钥派生函数
    ↓
派生密钥 (Derived Key)
    ↓
Keymaster派生 (Hardware-backed)
    ↓
CE加密密钥 (AES-256)
    ↓
内核fscrypt子系统

1.4 Fscrypt内核接口

Android通过libfscrypt库与内核fscrypt子系统交互:

// system/extras/libfscrypt/fscrypt.cpp

// 添加加密密钥到内核
bool fscrypt_add_key_to_keyring(const fscrypt_key& key,
                                const char* mountpoint) {
    android::base::unique_fd fd(open(mountpoint, O_RDONLY | O_DIRECTORY | O_CLOEXEC));

    struct fscrypt_add_key_arg arg;
    memset(&arg, 0, sizeof(arg));
    arg.key_spec.type = FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR;
    memcpy(arg.key_spec.u.descriptor, key.fek, FSCRYPT_KEY_DESCRIPTOR_SIZE);
    arg.raw_size = key.fek_size;
    memcpy(arg.raw, key.fek, key.fek_size);

    // ioctl系统调用
    if (ioctl(fd, FS_IOC_ADD_ENCRYPTION_KEY, &arg) != 0) {
        PLOG(ERROR) << "FS_IOC_ADD_ENCRYPTION_KEY failed";
        return false;
    }

    return true;
}

// 设置目录加密策略
bool fscrypt_policy_set(const char* directory,
                       const fscrypt_policy_v2& policy) {
    android::base::unique_fd fd(open(directory, O_RDONLY | O_DIRECTORY | O_CLOEXEC));

    // 使用v2策略(Android 11+)
    if (ioctl(fd, FS_IOC_SET_ENCRYPTION_POLICY, &policy) != 0) {
        PLOG(ERROR) << "FS_IOC_SET_ENCRYPTION_POLICY failed on " << directory;
        return false;
    }

    return true;
}

内核ioctl命令

// Linux kernel include/uapi/linux/fscrypt.h
#define FS_IOC_SET_ENCRYPTION_POLICY   _IOR('f', 19, struct fscrypt_policy_v2)
#define FS_IOC_GET_ENCRYPTION_POLICY   _IOW('f', 21, struct fscrypt_policy_v2)
#define FS_IOC_ADD_ENCRYPTION_KEY      _IOWR('f', 23, struct fscrypt_add_key_arg)
#define FS_IOC_REMOVE_ENCRYPTION_KEY   _IOWR('f', 24, struct fscrypt_remove_key_arg)

1.5 Keymaster HAL与硬件支持

Android 15中,密钥派生依赖Keymaster HAL(硬件支持的密钥管理):

// system/vold/KeyStorage.cpp

// 从Keymaster派生加密密钥
static bool generateKeymasterKey(Keymaster& keymaster, const KeyAuthentication& auth,
                                const std::string& appId, KeyBuffer* key) {
    // 1. 生成Keymaster密钥
    auto paramBuilder = km::AuthorizationSetBuilder()
                        .AesEncryptionKey(AES_KEY_BYTES * 8)
                        .GcmMode MinMacLength(GCM_MAC_BYTES * 8)
                        .Authorization(km::TAG_APPLICATION_ID, appId)
                        .Authorization(km::TAG_NO_AUTH_REQUIRED);

    if (auth.usesKeymaster()) {
        paramBuilder.Authorization(km::TAG_USER_SECURE_ID, auth.secureUserId);
    }

    km::AuthorizationSet in_params = paramBuilder.build();
    KeyBlob keyBlob;

    // 调用Keymaster HAL生成密钥
    auto result = keymaster.generateKey(in_params, &keyBlob);
    if (!result.isOk()) {
        LOG(ERROR) << "Keymaster generateKey failed: " << result.error();
        return false;
    }

    // 2. 从Keymaster导出密钥材料
    auto exportResult = keymaster.exportKey(km::KeyFormat::RAW, keyBlob,
                                           km::AuthorizationSet(),
                                           km::AuthorizationSet());

    *key = KeyBuffer(exportResult.blob.data(), exportResult.blob.size());
    return true;
}

硬件支持的好处

  • 密钥永不离开TEE(Trusted Execution Environment)
  • 抵御物理攻击(密钥提取)
  • 支持生物识别(指纹/人脸)认证

1.6 Android 15中的FBE增强

1.6.1 硬件包裹密钥(Hardware-Wrapped Keys)

// system/vold/KeyStorage.cpp - Android 15新特性
static KeyGeneration makeGen(const EncryptionOptions& options) {
    return KeyGeneration{
        FSCRYPT_MAX_KEY_SIZE,
        true,
        options.use_hw_wrapped_key  // ← Android 15: 硬件包裹密钥
    };
}

原理

  • 密钥由硬件生成,永不以明文形式出现在应用处理器
  • 密钥材料由硬件专用密钥包裹(wrap)
  • 解密操作必须在安全硬件内完成

1.6.2 密钥升级机制(Key Upgrade)

// system/vold/KeyStorage.cpp
bool reloadKeyFromSessionKeyring(const std::string& mountpoint, const EncryptionKey& key) {
    // Android 15: 支持在线密钥升级,无需重启
    auto result = keymaster.upgradeKey(key.blob, upgrade_params);
    if (result.isOk()) {
        // 热更新内核中的密钥
        return fscrypt_add_key_to_keyring(result.upgradedBlob, mountpoint);
    }
    return false;
}

二、f2fs文件系统核心特性

2.1 为什么Android选择f2fs?

传统ext4文件系统设计初衷是机械硬盘(HDD),而现代移动设备全部使用Flash存储(eMMC/UFS)。f2fs(Flash-Friendly File System)专为Flash优化:

ext4 vs f2fs对比

┌──────────────┬────────────┬────────────┐
│   特性        │    ext4    │    f2fs    │
├──────────────┼────────────┼────────────┤
│ 设计目标      │ HDD优化    │ Flash优化  │
│ 写入方式      │ 就地更新    │ LFS日志式  │
│ 碎片化       │ 容易碎片化  │ 天然抗碎片  │
│ GC机制       │ 无         │ 后台在线GC │
│ Trim支持     │ 基础支持    │ 深度集成   │
│ 随机写性能    │ 一般       │ 优秀       │
│ Android优化  │ 有限       │ 深度定制   │
└──────────────┴────────────┴────────────┘

2.2 f2fs核心设计:日志结构化

f2fs采用**LFS(Log-Structured File System)**设计:

┌────────────────────────────────────────┐
│          f2fs 磁盘布局                  │
├────────┬──────┬─────────┬─────────────┤
│ Super  │ CP   │  SSA    │   Main Area  │
│ block  │ area │  area   │  (Segments)  │
├────────┴──────┴─────────┴─────────────┤
│                                        │
│  Main Area详细:                        │
│  ┌──────────────────────────────────┐ │
│  │ HOT NODE  │ WARM NODE │ COLD NODE│ │ ← Node Segments
│  ├───────────┼───────────┼──────────┤ │
│  │ HOT DATA  │ WARM DATA │ COLD DATA│ │ ← Data Segments
│  └──────────────────────────────────┘ │
└────────────────────────────────────────┘

关键数据结构

// fs/f2fs/f2fs.h - Segment Info
struct f2fs_summary {
    __le32 nid;          // Node ID
    __u8 version;        // Node version
    __le16 ofs_in_node;  // Offset in node
} __packed;

// Checkpoint区域
struct f2fs_checkpoint {
    __le64 checkpoint_ver;      // CP版本号
    __le64 user_block_count;    // 用户数据块数
    __le64 valid_block_count;   // 有效块数
    __le32 rsvd_segment_count;  // 保留segment数
    __le32 overprov_segment_count;  // 过度配置segment数
    __le32 free_segment_count;  // 空闲segment数
    // ...
} __packed;

2.3 热/冷数据分离

f2fs根据数据更新频率分类:

// fs/f2fs/segment.h
enum {
    CURSEG_HOT_DATA = 0,   // 频繁修改的用户数据
    CURSEG_WARM_DATA,      // 中等频率数据
    CURSEG_COLD_DATA,      // 很少修改的数据
    CURSEG_HOT_NODE,       // 目录inode
    CURSEG_WARM_NODE,      // 文件inode
    CURSEG_COLD_NODE,      // 间接节点
    NR_CURSEG_TYPE,
};

分类策略

// fs/f2fs/segment.c
static int __get_segment_type(struct page *page, enum page_type p_type) {
    struct f2fs_io_info fio = {.type = p_type};

    if (p_type == DATA) {
        struct inode *inode = page->mapping->host;

        // 1. 目录数据 → HOT DATA
        if (S_ISDIR(inode->i_mode))
            return CURSEG_HOT_DATA;

        // 2. 多媒体文件 → COLD DATA
        if (is_cold_data(page))
            return CURSEG_COLD_DATA;

        // 3. 其他 → WARM DATA
        return CURSEG_WARM_DATA;
    }

    // Node数据分类...
    return __get_node_segment_type(page);
}

好处

  • 减少GC开销(冷数据很少搬移)
  • 提升写入性能(热数据集中写入)
  • 延长Flash寿命(减少写放大)

2.4 多头日志与并发写入

f2fs支持6个活跃segment同时写入:

// fs/f2fs/segment.c
static void allocate_segment_by_default(struct f2fs_sb_info *sbi,
                                        int type, bool force) {
    // 每种类型维护独立的curseg
    struct curseg_info *curseg = CURSEG_I(sbi, type);

    if (force || !has_not_enough_free_secs(sbi, 0, 0)) {
        // 分配新segment
        get_new_segment(sbi, &curseg->segno, NEW_SEC, type);
        curseg->next_blkoff = 0;
    }
}

并发写入优势

传统单日志:
  HOTWARMCOLDHOTWARM → ... (串行写入)

f2fs多头日志:
  HOT   ──→ Segment A
  WARM  ──→ Segment B  } 并行写入
  COLD  ──→ Segment C

2.5 在线碎片整理与GC

2.5.1 Segment清理机制

// fs/f2fs/gc.c
int f2fs_gc(struct f2fs_sb_info *sbi, struct f2fs_gc_control *gc_control) {
    unsigned int segno;
    int gc_type = gc_control->init_gc_type;

    // 1. 选择victim segment(最少有效块的segment)
    if (!get_victim_by_default(sbi, &segno, gc_type, NO_CHECK_TYPE, LFS)) {
        return -ENODATA;
    }

    // 2. 遍历victim segment中的有效块
    for (off = 0; off < sbi->blocks_per_seg; off++) {
        struct f2fs_summary sum;

        if (!is_valid_data_blkaddr(sbi, blkaddr))
            continue;

        // 3. 读取有效数据
        page = f2fs_get_read_data_page(inode, bidx, REQ_RAHEAD, &gc_type);

        // 4. 重新写入到新的segment
        f2fs_submit_page_write(&fio);
    }

    return 0;
}

GC触发条件

// fs/f2fs/gc.c
static bool should_do_gc(struct f2fs_sb_info *sbi) {
    // 1. 空闲空间不足
    if (free_sections(sbi) <= sbi->reserved_sections)
        return true;

    // 2. 用户主动触发(fstrim)
    if (gc_urgent_mode(sbi))
        return true;

    // 3. 脏segment过多
    if (dirty_segments(sbi) > sbi->blocks_per_seg * 2)
        return true;

    return false;
}

2.5.2 Android中的GC优化

Android通过sysfs接口控制GC行为:

# 查看GC统计信息
cat /sys/kernel/debug/f2fs/status

# 设置GC urgent模式(适合低电量/后台场景)
echo 1 > /sys/fs/f2fs/<device>/gc_urgent

# 手动触发GC
echo 1 > /sys/fs/f2fs/<device>/gc_urgent_sleep_time

2.6 Android 15中的f2fs增强

2.6.1 压缩支持(Compression)

// fs/f2fs/compress.c - Android 15新增
static int f2fs_compress_pages(struct compress_ctx *cc) {
    const struct f2fs_compress_ops *cops =
        f2fs_cops[F2FS_I_SB(cc->inode)->s_compress_algorithm];

    // 支持LZ4/LZO/ZSTD压缩算法
    ret = cops->compress_pages(cc);

    if (ret < 0) {
        // 压缩失败,使用原始数据
        return -EAGAIN;
    }

    // 压缩率不足,不值得压缩
    if (cc->clen >= cc->rlen - COMPRESS_HEADER_SIZE)
        return -EAGAIN;

    return 0;
}

启用f2fs压缩

# 挂载选项
mount -t f2fs -o compress_algorithm=lz4,compress_extension=* /dev/block/dm-0 /data

# 或修改fstab
/dev/block/dm-0 /data f2fs noatime,nosuid,nodev,compress_algorithm=lz4

压缩效果

  • 典型压缩率:30-50%
  • APK/DEX文件效果最佳
  • 对性能影响小于5%

2.6.2 Age Extent Cache

// fs/f2fs/extent_cache.c
struct extent_tree {
    struct rb_root_cached root;     // 红黑树根
    struct list_head node_list;     // extent节点链表
    rwlock_t lock;
    atomic_t node_cnt;              // extent节点数
    unsigned int largest_updated;   // 最大extent更新时间
    struct extent_info largest;     // 缓存最大extent
};

优势

  • 减少磁盘访问(extent信息缓存在内存)
  • 加速随机读取
  • Android 15默认启用

三、Metadata加密与完整性保护

3.1 dm-default-key设备映射器

Android 9引入metadata加密,在FBE之上增加额外保护层:

┌──────────────────────────────────────┐
│         应用数据 (FBE加密)            │
├──────────────────────────────────────┤
│    文件系统Metadata (dm-default-key) │
├──────────────────────────────────────┤
│         块设备层                      │
└──────────────────────────────────────┘

实现原理

// system/vold/MetadataCrypt.cpp
static bool mount_via_fs_mgr(const char* mount_point, const char* blk_device) {
    // 1. 创建dm-default-key设备
    android::dm::DeviceMapper& dm = android::dm::DeviceMapper::Instance();

    std::string dm_name = "default-key";
    DmTable table;

    // 2. 配置dm目标参数
    std::string target_params = StringPrintf(
        "%s 0 1 allow_discards sector_size:4096 "
        "iv_large_sectors default_key=1", blk_device);

    table.Emplace<DmTargetDefaultKey>(0, fs_stat.st_size / 512, target_params);

    // 3. 激活dm设备
    if (!dm.CreateDevice(dm_name, table, &dm_path, 5s)) {
        return false;
    }

    // 4. 在dm设备上挂载文件系统
    return mount(dm_path.c_str(), mount_point, "f2fs",
                MS_NOATIME | MS_NOSUID | MS_NODEV, nullptr) == 0;
}

密钥管理

// system/vold/MetadataCrypt.cpp
static bool read_key(const std::string& metadata_key_dir,
                    const KeyGeneration& gen, KeyBuffer* key) {
    // Metadata密钥存放在/metadata分区
    std::string key_path = metadata_key_dir + "/key";

    if (pathExists(key_path)) {
        // 读取现有密钥
        return retrieveKey(key_path, kEmptyAuthentication, key);
    } else {
        // 首次启动,生成新密钥
        if (!generateStorageKey(gen, key)) return false;
        return storeKey(key_path, user_key_temp, *key);
    }
}

3.2 fsverity完整性验证

Android 11引入fsverity,防止文件被篡改:

# 为APK文件启用fsverity
fsverity enable /data/app/com.example.app/base.apk

# 查看fsverity状态
fsverity measure /data/app/com.example.app/base.apk

内核实现

// fs/verity/verify.c
bool fsverity_verify_page(struct page *page) {
    struct inode *inode = page->mapping->host;
    const struct fsverity_info *vi = inode->i_verity_info;

    // 1. 计算页面的Merkle树哈希
    u8 real_hash[FS_VERITY_MAX_DIGEST_SIZE];
    hash_at_level(vi, page, 0, real_hash);

    // 2. 从Merkle树中读取期望哈希
    u8 expected_hash[FS_VERITY_MAX_DIGEST_SIZE];
    get_hash_from_tree(vi, page->index, expected_hash);

    // 3. 比较哈希值
    if (memcmp(real_hash, expected_hash, vi->tree_params.digest_size) != 0) {
        pr_warn("fsverity: page %lu of file %s has mismatched hash\n",
                page->index, inode->i_name);
        return false;
    }

    return true;
}

Merkle树结构

              Root Hash (存储在inode)
                    │
        ┌───────────┴───────────┐
     Hash 0-3              Hash 4-7
        │                     │
   ┌────┴────┐           ┌────┴────┐
Hash 0  Hash 1        Hash 4  Hash 5
   │       │             │       │
Page 0  Page 1       Page 4  Page 5

四、存储性能分析与优化

4.1 I/O性能基准测试

4.1.1 使用dd测试顺序读写

# 测试顺序写入性能
dd if=/dev/zero of=/data/local/tmp/testfile bs=1M count=1024 oflag=direct

# 输出示例:
# 1024+0 records in
# 1024+0 records out
# 1073741824 bytes (1.0GB) copied, 4.235s, 254MB/s

# 测试顺序读取性能
dd if=/data/local/tmp/testfile of=/dev/null bs=1M iflag=direct

# 清理缓存后再测试(更准确)
echo 3 > /proc/sys/vm/drop_caches
dd if=/data/local/tmp/testfile of=/dev/null bs=1M iflag=direct

4.1.2 使用fio测试随机I/O

# Android系统需要先推送fio二进制
adb push fio /data/local/tmp/
adb shell chmod +x /data/local/tmp/fio

# 4K随机读测试
adb shell /data/local/tmp/fio \
  --name=randread \
  --ioengine=libaio \
  --iodepth=32 \
  --rw=randread \
  --bs=4k \
  --direct=1 \
  --size=1G \
  --numjobs=4 \
  --runtime=60 \
  --group_reporting

# 输出关键指标:
# IOPS(每秒I/O操作数)
# BW(带宽)
# lat(延迟)

4.2 存储性能诊断流程

06-02-storage-performance-analysis-flowchart.png

4.2.1 使用systrace追踪I/O

# 捕获存储相关trace
python systrace.py -o trace.html \
  -t 10 \
  disk \
  block \
  f2fs \
  ext4 \
  sched \
  freq

# 分析trace文件:
# 1. 查找"f2fs_write_begin"/"f2fs_write_end"事件
# 2. 分析I/O等待时间
# 3. 检查是否有大量sync操作

4.2.2 使用perfetto深度分析

# 配置perfetto trace
cat > /data/local/tmp/trace_config.pbtxt << EOF
buffers: {
    size_kb: 102400
    fill_policy: RING_BUFFER
}

data_sources: {
    config {
        name: "linux.ftrace"
        ftrace_config {
            ftrace_events: "f2fs/f2fs_write_begin"
            ftrace_events: "f2fs/f2fs_sync_file_enter"
            ftrace_events: "block/block_rq_issue"
            ftrace_events: "block/block_rq_complete"
        }
    }
}

duration_ms: 10000
EOF

# 捕获trace
perfetto -c /data/local/tmp/trace_config.pbtxt -o /data/local/tmp/trace.perfetto

4.2.3 f2fs状态监控

# 查看f2fs实时状态
cat /sys/kernel/debug/f2fs/status

# 输出示例(关键字段解读):
# =============  DATA SECTION  =============
# Total sections: 3072
# Free sections: 256           ← 空闲section数(越大越好)
# Dirty segments: 45          ← 待GC的segment(过高需优化)
#
# =============  DIRTY SEGMENTS  ===========
# Dirty type: HOT DATA  ( 12) ← 各类型脏数据统计
# Dirty type: WARM DATA ( 18)
# Dirty type: COLD DATA ( 15)
#
# =============  GC STATISTICS  ============
# GC calls: 1234              ← GC触发次数
# GC reclaimed blocks: 56789  ← 回收的块数

4.3 常见性能问题与解决方案

问题1:写入性能突然下降

诊断

# 1. 检查空闲空间
df -h /data
# 输出:Filesystem      Size  Used Avail Use%
#       /dev/dm-0       64G   62G   2G  97%  ← 空间不足!

# 2. 检查GC压力
cat /sys/kernel/debug/f2fs/status | grep "Dirty segments"
# Dirty segments: 512  ← 过高,需要GC

# 3. 查看是否有大量sync操作
systrace.py -t 5 disk | grep "f2fs_sync_fs"

解决方案

# 方案A:手动触发fstrim(清理无效块)
fstrim -v /data
# 输出:/data: 8.5 GiB (9123456789 bytes) trimmed

# 方案B:启用GC urgent模式
echo 1 > /sys/fs/f2fs/dm-0/gc_urgent
# 等待GC完成后
echo 0 > /sys/fs/f2fs/dm-0/gc_urgent

# 方案C:清理缓存数据(释放空间)
pm trim-caches 10G  # 清理10GB缓存

问题2:随机I/O性能差

诊断

# 使用iobench测试4K随机读
iobench --type=randread --size=1G --bs=4096 --iodepth=32

# 输出:
# IOPS: 1200  ← 正常值应>5000(UFS 2.1)

优化方案

# 1. 启用readahead
echo 256 > /sys/block/sda/queue/read_ahead_kb

# 2. 调整f2fs inline_xattr(减少元数据碎片)
mount -o remount,inline_xattr /data

# 3. 启用f2fs的age_extent_cache(Android 15新特性)
# 需要在fstab中配置:
/dev/block/dm-0 /data f2fs noatime,age_extent_cache

问题3:空间占用异常

诊断

# 1. 分析目录大小
du -sh /data/* | sort -h | tail -10

# 2. 查找大文件
find /data -type f -size +100M -exec ls -lh {} \;

# 3. 检查f2fs的over-provisioning
cat /sys/kernel/debug/f2fs/status | grep "overprov"
# Overprovision segments: 512  ← OP空间(5-10%正常)

解决方案

# 方案A:启用f2fs压缩(需要Android 12+)
# 在fstab中添加compress选项
/dev/block/dm-0 /data f2fs compress_algorithm=lz4,compress_extension=apk:dex:jar

# 方案B:调整reserved_blocks(预留块)
echo 100 > /sys/fs/f2fs/dm-0/reserved_blocks

# 方案C:清理tombstone和日志
rm -rf /data/tombstones/*
logcat -c

4.4 f2fs关键调优参数

4.4.1 GC相关参数

# gc_urgent: 紧急GC模式
# 0 = 关闭, 1 = 开启
echo 1 > /sys/fs/f2fs/<device>/gc_urgent

# gc_urgent_sleep_time: GC休眠时间(毫秒)
# 建议值:100-500ms(平衡性能与功耗)
echo 500 > /sys/fs/f2fs/<device>/gc_urgent_sleep_time

# min_fsync_blocks: 触发fsync前的最小块数
# 较大值可减少sync次数,但增加数据丢失风险
echo 8 > /sys/fs/f2fs/<device>/min_fsync_blocks

4.4.2 挂载选项优化

# fstab配置示例(/vendor/etc/fstab.device)
/dev/block/dm-0 /data f2fs noatime,nosuid,nodev,discard,reserve_root=32768,resgid=1065,fsync_mode=nobarrier,inlinecrypt,compress_algorithm=lz4

# 关键选项解读:
# - noatime: 禁用访问时间更新(减少写入)
# - discard: 启用TRIM(及时释放无效块)
# - reserve_root=32768: 预留32MB给root用户
# - fsync_mode=nobarrier: 减少fsync屏障(性能优先)
# - inlinecrypt: 使用硬件加密(减少CPU开销)
# - compress_algorithm=lz4: 启用LZ4压缩

4.4.3 Android特有优化

// system/vold/fs/F2fs.cpp - Android默认挂载选项
status_t Mount(const std::string& source, const std::string& target) {
    std::vector<std::string> args;
    args.push_back("-o");

    // Android 15优化选项
    std::string options = "noatime,nosuid,nodev,discard";
    options += ",reserve_root=32768,resgid=1065";
    options += ",inlinecrypt";  // 硬件加速加密
    options += ",fsync_mode=nobarrier";  // 减少sync屏障

    // 如果支持压缩
    if (android::base::GetBoolProperty("ro.storage.compress", false)) {
        options += ",compress_algorithm=lz4";
        options += ",compress_extension=apk:jar:dex:so";
    }

    args.push_back(options);
    args.push_back(source);
    args.push_back(target);

    return ForkExecvp("/system/bin/mount.f2fs", args);
}

五、实战:存储问题诊断案例

案例1:应用启动慢,怀疑存储性能问题

现象

  • 应用冷启动时间从1秒增加到3秒
  • systrace显示大量时间在I/O等待

诊断步骤

# 1. 捕获应用启动trace
adb shell am start -W -n com.example.app/.MainActivity

# 输出:
# TotalTime: 3248  ← 总启动时间3.2秒

# 2. 使用systrace分析
python systrace.py -o app_start.html -t 5 -a com.example.app sched freq disk

# 3. 在trace中发现大量f2fs_sync_file调用
# 定位代码:应用在启动时同步写入SharedPreferences

解决方案

// 问题代码:
SharedPreferences.Editor editor = prefs.edit();
editor.putString("key", "value");
editor.commit();  // ← 同步写入,阻塞主线程

// 优化后:
SharedPreferences.Editor editor = prefs.edit();
editor.putString("key", "value");
editor.apply();  // ← 异步写入,不阻塞

效果验证

adb shell am start -W -n com.example.app/.MainActivity
# TotalTime: 1156  ← 优化后降至1.1秒

案例2:设备使用一段时间后变慢

现象

  • 新设备流畅,使用3个月后明显卡顿
  • 存储剩余空间小于10%

诊断

# 1. 检查f2fs状态
adb shell cat /sys/kernel/debug/f2fs/status

# 输出异常:
# Free sections: 45        ← 空闲section过少
# Dirty segments: 789      ← 大量脏segment待GC
# Valid blocks: 95%        ← 有效块占比过高,GC效率低

# 2. 检查碎片化程度
adb shell /data/local/tmp/f2fs_io fsstat /data

# 输出:
# - Main: 98.5% usage
# - Fragmentation: High  ← 严重碎片化

解决方案

# 步骤1:触发aggressive GC
adb shell "echo 1 > /sys/fs/f2fs/dm-0/gc_urgent"
adb shell "echo 100 > /sys/fs/f2fs/dm-0/gc_urgent_sleep_time"

# 等待5分钟让GC运行
sleep 300

# 步骤2:fstrim释放无效块
adb shell fstrim -v /data
# 输出:/data: 12.3 GiB trimmed

# 步骤3:清理应用缓存(释放空间)
adb shell pm trim-caches 10G

# 步骤4:关闭gc_urgent
adb shell "echo 0 > /sys/fs/f2fs/dm-0/gc_urgent"

# 验证效果
adb shell cat /sys/kernel/debug/f2fs/status
# Free sections: 256       ← 空闲section恢复
# Dirty segments: 52       ← 脏segment大幅减少

预防措施(系统配置):

# 配置自动fstrim(通过init.rc)
service fstrim_periodic /system/bin/fstrim /data
    class late_start
    oneshot
    seclabel u:r:fstrim:s0

# 或使用JobScheduler定时触发

案例3:加密导致性能下降

现象

  • 读写性能比预期慢30%
  • 使用了FBE加密

诊断

# 1. 确认是否使用硬件加密
adb shell getprop ro.crypto.fde_algorithm
# 输出:aes-256-xts  ← 如果是软件加密,需要优化

# 2. 检查Keymaster HAL版本
adb shell dumpsys keystore | grep "Hardware"
# 输出:Hardware-backed keystore: true  ← 硬件支持

# 3. 测试加密性能
# 关闭FBE后测试(需要factory reset,仅用于对比)
dd if=/dev/zero of=/data/local/tmp/test bs=1M count=1024 oflag=direct
# 有FBE: 180 MB/s
# 无FBE: 250 MB/s ← 差距30%

优化方案

// 1. 确保使用硬件加速加密(fstab配置)
/dev/block/dm-0 /data f2fs noatime,inlinecrypt  // ← 添加inlinecrypt

// 2. 在Kernel配置中启用Inline Crypto Engine(ICE)
CONFIG_CRYPTO_DEV_QCOM_ICE=y  // Qualcomm平台
CONFIG_SCSI_UFS_CRYPTO=y      // UFS硬件加密

// 3. 检查是否使用hardware-wrapped keys(Android 15)
// 在KeyStorage中启用:
KeyGeneration gen = {
    .keysize = 32,
    .allow_gen = true,
    .use_hw_wrapped_key = true  // ← 启用硬件包裹密钥
};

验证

# 重新测试性能
dd if=/dev/zero of=/data/local/tmp/test bs=1M count=1024 oflag=direct
# 优化后: 238 MB/s  ← 性能恢复到接近无加密水平

六、开发者最佳实践

6.1 应用层存储优化建议

6.1.1 减少不必要的fsync

// ❌ 错误:频繁sync
for (int i = 0; i < 1000; i++) {
    FileOutputStream fos = new FileOutputStream(file, true);
    fos.write(data);
    fos.getFD().sync();  // 每次写入都sync,性能极差
    fos.close();
}

// ✅ 正确:批量写入后sync一次
FileOutputStream fos = new FileOutputStream(file);
for (int i = 0; i < 1000; i++) {
    fos.write(data);
}
fos.getFD().sync();  // 只sync一次
fos.close();

6.1.2 合理使用SharedPreferences

// ❌ 错误:commit()阻塞主线程
SharedPreferences.Editor editor = prefs.edit();
editor.putString("key", value);
editor.commit();  // 同步I/O

// ✅ 正确:使用apply()异步写入
editor.apply();  // 异步I/O,不阻塞

// ✅ 批量操作
SharedPreferences.Editor editor = prefs.edit();
editor.putString("key1", value1);
editor.putString("key2", value2);
editor.apply();  // 一次性写入多个key

6.1.3 大文件写入优化

// ✅ 使用BufferedOutputStream减少系统调用
try (BufferedOutputStream bos = new BufferedOutputStream(
        new FileOutputStream(file), 8192)) {  // 8KB缓冲区
    bos.write(largeData);
}

// ✅ 使用FileChannel和MappedByteBuffer(更快)
try (FileChannel channel = FileChannel.open(file.toPath(),
        StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
    MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE,
                                          0, largeData.length);
    buffer.put(largeData);
}

6.2 Framework层优化

6.2.1 调整Linux I/O调度器

# 查看当前I/O调度器
cat /sys/block/sda/queue/scheduler
# 输出:[mq-deadline] none

# 推荐配置(Android设备):
# - UFS存储:mq-deadline 或 none(UFS自带调度)
# - eMMC存储:cfq 或 deadline

# 修改调度器
echo "mq-deadline" > /sys/block/sda/queue/scheduler

6.2.2 调整写回策略

# 降低dirty_ratio(加快写回)
echo 10 > /proc/sys/vm/dirty_ratio  # 默认20
echo 5 > /proc/sys/vm/dirty_background_ratio  # 默认10

# 缩短写回超时
echo 1500 > /proc/sys/vm/dirty_writeback_centisecs  # 15秒(默认30秒)

6.3 SELinux与存储权限

# 确保f2fs相关SELinux策略正确
# file_contexts示例:
/data/vendor/f2fs(/.*)? u:object_r:vendor_f2fs_file:s0

# sepolicy示例:
allow system_server vendor_f2fs_file:dir { read open getattr search };
allow vold vendor_f2fs_file:dir { create write add_name };

七、问题诊断速查表

7.1 性能问题快速定位

症状可能原因诊断命令解决方案
写入慢空间不足/GC压力大df -h + cat /sys/kernel/debug/f2fs/statusfstrim + 清理缓存
读取慢缓存失效/碎片化echo 3 > /proc/sys/vm/drop_caches 后测试调整readahead + GC
随机I/O差extent缓存未命中iobench --type=randread启用age_extent_cache
应用启动慢频繁syncsystrace disk改用异步I/O(apply)
设备越用越慢碎片化 + 空间不足f2fs_io fsstat /data定期fstrim + 增加OP空间

7.2 加密问题诊断

# 检查FBE状态
adb shell getprop ro.crypto.state
# 输出:encrypted  ← FBE已启用

# 检查密钥状态
adb shell dumpsys activity service com.android.server.locksettings
# 查找:User 0: CE key installed

# 检查硬件加密支持
adb shell getprop ro.hardware.keystore
# 输出:trusty  ← 使用TEE

# 检查Keymaster版本
adb shell dumpsys keystore | grep "Hardware"

7.3 f2fs健康检查

# 综合健康检查脚本
adb shell << 'EOF'
echo "=== F2FS Health Check ==="

# 1. 基本信息
echo "Filesystem:"
df -h /data

# 2. f2fs状态
echo -e "\nf2fs Status:"
cat /sys/kernel/debug/f2fs/status | grep -E "Free sections|Dirty segments|Valid blocks"

# 3. GC状态
echo -e "\nGC Status:"
cat /sys/kernel/debug/f2fs/status | grep -E "GC calls|reclaimed"

# 4. 挂载选项
echo -e "\nMount Options:"
mount | grep /data

# 5. 磁盘I/O统计
echo -e "\nDisk Stats:"
cat /proc/diskstats | grep dm-0

echo "=== Check Complete ==="
EOF

八、总结与展望

8.1 核心要点回顾

  1. FBE加密机制

    • CE/DE双密钥体系实现Direct Boot
    • 基于Keymaster HAL的硬件安全保障
    • Android 15增强:硬件包裹密钥、在线密钥升级
  2. f2fs文件系统

    • LFS日志结构针对Flash优化
    • 热/冷数据分离减少写放大
    • 在线GC和碎片整理
    • Android 15新特性:压缩支持、age extent cache
  3. Metadata加密

    • dm-default-key设备映射器
    • fsverity完整性保护
  4. 性能优化方法论

    • 基准测试(dd/fio/iobench)
    • Trace分析(systrace/perfetto)
    • GC调优(gc_urgent/fstrim)
    • 应用层优化(避免sync/使用apply)

8.2 Android 15的改进

特性Android 14Android 15提升
FBE硬件加密支持Inline Crypto强制Hardware-wrapped keys安全性↑
f2fs压缩实验性默认启用(LZ4)空间节省30%+
Metadata加密dm-cryptdm-default-key优化性能↑15%
extent缓存基础extent_cacheage_extent_cache随机读↑25%
GC效率标准GC自适应GC + 预测算法后台性能↑

8.3 未来展望

存储技术演进方向

  • UFS 4.0普及:顺序读取速度将达4GB/s
  • ZNS(Zoned Namespace)SSD:更好的Flash管理
  • 持久化内存(PMem):字节级寻址的存储
  • AI驱动的存储优化:机器学习预测I/O模式

Android存储系统发展

  • 更智能的GC策略(基于用户行为学习)
  • 更细粒度的加密控制(per-file key)
  • 存储分层(热数据使用高速存储)
  • 端到端加密与云同步集成

九、参考资源

9.1 源码路径

# FBE加密相关
system/vold/FsCrypt.cpp                    # FBE核心实现
system/vold/KeyStorage.cpp                 # 密钥存储
system/extras/libfscrypt/fscrypt.cpp       # libfscrypt库
kernel/fs/crypto/                          # 内核fscrypt子系统

# f2fs文件系统
kernel/fs/f2fs/                            # f2fs内核代码
external/f2fs-tools/                       # f2fs用户空间工具
system/core/fs_mgr/                        # 文件系统管理器

# 设备映射器
system/core/libdm/                         # dm设备映射器库
system/vold/MetadataCrypt.cpp              # Metadata加密

9.2 官方文档

9.3 性能分析工具

# 系统工具
adb shell dumpsys diskstats              # 磁盘统计
adb shell dumpsys storaged               # 存储守护进程
adb shell /system/bin/f2fs_io            # f2fs专用工具
adb shell /system/bin/ioctl              # 底层ioctl测试

# 第三方工具
fio                                      # 灵活的I/O测试工具
iobench                                  # 简单的benchmark
blktrace                                 # 块层追踪

9.4 调试命令汇总

# FBE调试
getprop ro.crypto.type                   # 查看加密类型
getprop ro.crypto.state                  # 查看加密状态
vdc cryptfs checkpw                      # 检查密码(需要root)

# f2fs调试
cat /sys/kernel/debug/f2fs/status        # f2fs状态
cat /sys/fs/f2fs/<device>/features       # f2fs特性支持
f2fs_io gc /data                         # 手动触发GC

# 性能调试
systrace.py disk block f2fs              # trace存储I/O
perfetto --config trace.pbtxt            # 深度性能分析
simpleperf record -a -g --duration 10    # CPU采样分析

系列文章


欢迎来我中的个人主页找到更多有用的知识和有趣的产品