
UFS逻辑单元管理听起来像是一个只在芯片原厂FAE或者存储方案公司里才会被认真研究的冷门话题但我在实际项目中遇到的工程师十个里有八个都是被“为什么同一个UFS芯片在不同板子上分出来的盘不一样”或者“为什么系统分区老是写坏”这类问题绊住之后才开始回头补逻辑单元的课。UFS叫Universal Flash Storage是JEDEC定义的通用闪存存储标准手机上用得最多平板、车载、服务器引导盘里也越来越常见。而逻辑单元Logical Unit是UFS设备内部能被主机端单独寻址和操作的存储实体你平时看到的sda1、userdata分区本质上都是逻辑单元在操作系统里的投影。这篇内容我按自己的经验整理从UFS设备的基本结构、逻辑单元的类型划分到查询、配置、TRIM这些实操命令最后再聊一些踩过坑才明白的排障思路。不管你是刚接触UFS的嵌入式工程师还是在做存储方案选型的产品经理应该都能在里面找到能直接用的东西。1. UFS 逻辑单元是什么为什么重要1.1 UFS设备的基本构成一个UFS设备从物理上看就是一颗SoC加几片NAND闪存封装在一起。控制器负责FTL、坏块管理、ECC校验和磨损均衡这些工作在设备内部完成主机端根本不关心闪存物理块是怎么分布的。主机通过UFS主机控制器接口UFSHCI发出命令经过UniPro和M-PHY物理链路到达UFS设备整个数据交换过程由UPIUUFS Protocol Information Unit协议信息单元来承载。UPIU里有一个LUN字段专门指向目标逻辑单元。很多人第一次看到UFS协议栈会觉得复杂但换个角度想就通了这一整套东西本质上就是把一套SCSI命令集塞进了为闪存而优化的通信链路里。UFS设备对外表现出的是一块SCSI磁盘磁盘内部又进一步划分出若干可独立操作的逻辑单元。主机没法直接访问闪存物理地址只能通过逻辑单元的LBA块地址来读写。1.2 从SCSI继承的“逻辑单元”概念SCSI体系里有一种经典寻址模型一个Target设备下可以挂多个LUN主机通过LUN号区分不同的逻辑单元。UFS直接把这套东西继承了下来只是把物理链路从并行SCSI换成了M-PHY把传输协议改成了UPIU命令集则沿用SPC/SBC标准的SCSI命令。所以你如果会调试SCSI硬盘那UFS的很多命令操作会让你感到熟悉只是底层包了一层UFS特有的事务机制。这也是为什么很多Linux工具比如sg3_utils、lsscsi可以直接用在UFS设备上。它们本来是为SCSI设备写的但UFS在软件层面就是按SCSI磁盘的样子暴露给系统的。做嵌入式开发的朋友应该深有体会板上跑起来之后/dev/sda这个节点背后的设备很可能就是一颗UFS存储芯片。1.3 逻辑单元如何影响日常存储我见过不少踩坑案例最后都归结到没理解逻辑单元的分隔作用。第一逻辑单元提供了天然的物理隔离能力同一个UFS芯片可以切出系统区、用户数据区、缓存区、OTA区区与区之间互不干扰。第二不同逻辑单元可以设置不同属性比如系统区设置为只读或永久写保护用户区保持可写并开启TRIM固件区设置为增强区域来保障随机写性能。第三UFS支持多队列和并发访问主机可以同时向不同LUN发出读写命令合理规划逻辑单元能让设备在多线程场景下跑出更好的实际带宽。换句话说逻辑单元不是简单的“分区表里的分区”它背后关联着设备描述符、配置描述符、容量分配、性能属性和安全机制。管理好逻辑单元才能在一个容量固定的UFS芯片上做出稳定、高效、可维护的存储方案。2. UFS 逻辑单元的类型与属性配置2.1 通用LU、引导LU与RPMB的职责划分UFS设备里常见的逻辑单元分几类。通用LU最常见就是给操作系统装数据用的一般有多个每个可以独立设置容量和属性。引导LU最多3个早期规范里叫Boot LU A/B/C主要放引导代码。手机在启动阶段SoC从引导LU里读启动镜像等内核接管以后再切到通用LU上的userdata分区。很多设备平时会把引导LU隐藏掉防止系统或者误操作把引导数据擦掉。RPMBReplay Protected Memory Block是一种带安全认证功能的逻辑单元用来存钥匙、计数器这类敏感数据。它靠HMAC签名和重放计数机制保证写操作来自合法主机读操作也要经过认证所以RPMB不是随便发个READ命令就能读的。如果你在项目里碰到RPMB报错多半不是存储芯片坏了而是主机侧的安全密钥或计数器状态没对齐。除了这三种有些厂商还会把固件更新区单独做成一个LU让升级固件时不会碰到用户数据区。所以你要是真拿到一颗UFS设备请先别急着往里写数据把它的LU类型和分布梳理清楚再动手省得后面给自己挖坑。2.2 LU描述符与配置描述符每个逻辑单元都有自己的描述符叫做LU描述符Unit Descriptor里面记录着LUN号、LU类型、逻辑块大小、总块数容量、写保护状态、增强区域属性等信息。而整个设备层面还有一个配置描述符Configuration Descriptor里面定义了设备启用了哪些LU、每个LU的起始LBA范围、大小和类型。这两个描述符可以说是逻辑单元管理的“总纲”。我每次拿到新UFS设备第一件事就是读取配置描述符把里面的字节流解析成表格。你会看到设备版本、每个LU的使能位、起始地址、容量、类型代码。这一步能避免很多低级错误比如两块LU容量重叠或者某个LU根本没启用但系统还在尝试挂载。配置描述符是可以写的修改之后设备会重新加载配置但这个过程必须配合设备复位或重新上电否则新配置不生效。2.3 写保护、增强区域等实用属性逻辑单元的属性里有几个在实际项目里非常关键。写保护属性分两种永久写保护和电源周期写保护。永久写保护一旦设置就没办法取消适合出厂固件、密钥区电源周期写保护是断电后失效适合临时要防误写的场景。增强区域Enhanced Area则是一些UFS设备支持的容量类型它内部可能针对随机写做了优化适合放需要频繁更新的数据。很多方案把用户数据区、应用缓存区设成增强区域把系统区、备份区放在普通区这样既保证体验又控制成本。逻辑单元还有块大小这个属性常见的是4096字节逻辑块。这个大小直接影响主机侧命令的参数计算比如LBA和传输长度的单位。很多人在调读写时发现“地址怎么对不上”其实就是因为把逻辑块大小默认当成了512字节去换算。拿到设备后第一步还是老老实实读一遍每个LU的属性别猜。3. LU 管理实操识别、查询与命令3.1 系统层面识别UFS设备的逻辑单元在Linux系统里UFS设备初始化成功后dmesg里会出现与ufshcd类似的日志然后系统会注册一个SCSI磁盘设备。用lsscsi或者sg_scan查看能看到厂商名、产品名、固件版本还有对应的LUN编号。要是UFS设备里有多个通用LU系统可能就会注册出多个块设备也可能是一个块设备带多个分区具体取决于配置描述符和分区表。Android手机上更常见的是用adb方式查看。连进设备后在/sys/block/下能看到sda、sdb等节点再往下翻device目录能读到UFS相关的型号和LUN信息。厂商调试工具一般也会提供读配置描述符和LU属性的入口这些工具在量产测试阶段几乎是必需品。真要手动操作Linux下的sg3_utils套装就是最趁手的利器。3.2 查询与配置QUERY REQUEST 使用流程UFS协议里专门有一个叫Query Request的机制用来读写描述符、属性和标志位。想了解设备支持哪些LU、每个LU什么情况就靠它。查询逻辑单元信息的基本流程是这样主机侧构造一个Command UPIU命令操作码设为Query Request。在查询请求中指明查询功能比如Read Descriptor、描述符类型比如Configuration Descriptor或Unit Descriptor、目标LUN。把UPIU发到UFS设备设备处理完返回Response UPIU数据内容在Data In阶段带回来。把返回的字节流按描述符格式解析出来得到各个LU的容量、类型、属性。读取只是第一步要是想改配置使用Write Descriptor功能把修改后的整段描述符写回设备。写完后一定记得让设备复位或者重新上电让固件重新加载配置。我第一次改配置描述符时改完没复位就急着读写结果设备返回的全是保留值折腾了半天才发现是没复位。这个坑希望你们别踩。3.3 日常管理常用SCSI命令集合下面这张表是我在调试UFS设备时最常用到的SCSI命令都和逻辑单元管理有关。操作目标SCSI命令典型用途基础识别INQUIRY获取厂商、产品型号、固件版本容量查询READ CAPACITY(10/16)获取某LU的逻辑块数量和块大小数据读写READ(10)、WRITE(10)按LBA对某个LU执行读写缓存同步SYNCHRONIZE CACHE(10)把整机缓存中的数据刷到介质空间释放UNMAP对应TRIM操作标记无效LBA设备启停START STOP UNIT让设备进入/退出低功耗状态READ CAPACITY有个细节值得提它返回的“最后一块LBA”和“块大小”是后续所有读写的基础。换算地址时LBA从0开始算实际可寻址范围是0到返回的LBA值。如果你看到某个LU的容量只有标称值的一半先别怀疑芯片缩水多半是有其他LU占用了额外空间或者Boot/RPMB区域被误映射了出来。3.4 一次完整的TRIM/UNMAP操作过程很多人在搜“UFS有没有TRIM命令”的时候其实问的就是UNMAP。UFS设备是支持TRIM的它对应的SCSI命令就是UNMAP。操作系统层面的fstrim最终也是通过UNMAP把不再使用的LBA告诉UFS设备。我来拆解一次UNMAP操作的完整流程。假设我们的通用LU块大小是4096字节要释放的逻辑块范围是从LBA 1024开始的128个块。构造UNMAP命令时CDB里需要带上参数块长度后面是Unmap LBA表表里每条记录包含起始LBA和块数量。具体到字节上一个简化的例子是这样的操作码: 0x42 分配长度: 本命令参数块总字节数 Unmap LBA记录: 起始LBA: 0x0000000000000400 块数量: 0x00000080主机把这条命令封装成Command UPIU发下去UFS设备根据这些LBA信息把对应逻辑块标记为可回收。之后设备的垃圾回收机制会把这些块释放出来变成后续写入可用的空闲块。从实际维护角度看如果你想给Linux系统下的UFS分区执行TRIM直接使用fstrim -v /挂载点就能触发。跑一次之后可以看到它报告释放了多少字节。要是这个值长期接近0说明文件系统层可能没怎么产生无效数据或者驱动没有正确透传UNMAP。生产环境中UFS设备的TRIM策略和GC策略配合得好不好直接影响长期写入性能和寿命值得专门压测。4. 逻辑单元管理常见问题与排查技巧4.1 逻辑单元识别不到或容量显示异常这一类问题在项目中遇到得最多原因通常是几个方向。首先检查UFS设备有没有成功初始化链路层没训练成功时主机根本看不到任何LUN这个问题和逻辑单元本身关系不大更多在PHY、供电和参考时钟上。其次是看配置描述符里每个LU的使能位和容量字段是不是把引导LU和RPMB也算进通用容量了。我见过一台设备的userdata分区始终比预期小2GB查到最后发现是配置描述符里某块区域被显式映射给了一个隐藏LU导致通用LU的实际可用空间被压缩。还有一个容易忽略的点某些UFS设备在出厂时默认只对外开放一个或多达几个通用LU真要开放更多LU需要修改配置描述符并复位设备。系统侧如果找不到第二个LU先确认硬件设备是否真的把一个以上的LU映射出来了。4.2 写性能下降与GC/TRIM的关系写性能越来越差是UFS使用一段时间后的常见现象。硬件上闪存的垃圾回收GC如果长期压力大写入延迟就会明显升高。这里的核心逻辑是文件系统删除文件后对应LBA如果没有通过UNMAP告知UFS设备设备内部就不知道这些页已经无效后续GC还要把它们搬来搬去白白增加写放大拉低性能。解决办法就是定期执行TRIM。Android系统里其实有后台的fstrim机制很多Linux发行版也有每周定时任务。但有些定制系统为了省电把这个服务关掉了就会出现在持续使用一段时间后越来越卡的情况。项目阶段压测时可以对比开TRIM和不开TRIM两轮长时间写测试通常能明显看到不同。另外在GC压力峰值期间如果主机还在发大量写请求部分UFS固件会返回BUSY或者需要主机重试。日志里如果反复出现命令超时或重试最好给GC留一点空闲时间或者调整固件的GC策略参数具体能调什么取决于厂商开放程度。4.3 RPMB安全单元使用失败的排查思路RPMB这块报错通常不是芯片坏了而是主机侧安全上下文没准备好。最常见的情况是RPMB里的密钥没有写进去主机在执行写操作时返回认证失败。RPMB密钥的写入是一次性的需要在安全启动流程或产线初始化阶段完成之后主机侧和芯片侧要用同一个密钥来算HMAC。RPMB读操作也会校验MAC如果主机侧的counter值和设备侧不一致就会出现计数器校验错误。多次认证失败会导致设备侧的验证尝试计数增长有些设备还会因此在等待一段时间后才能重试。遇到这类问题时我的排查顺序是确认密钥是否已经写入、确认nonce和counter是否单独累加、确认MAC计算方式是否符合规范。RPMB不像普通LU直接发个命令就能看到数据必须先建立信任关系。4.4 不同操作系统与平台的实现差异同样一颗UFS芯片在Linux、Android、Windows和车机平台上的表现方式差别很大。Linux和Android基本走SCSI块设备层可以通过sg3_utils、fstrim这些标准工具来操作。Windows对UFS的通用支持没那么标准主板上挂UFS设备的情况也比较少更多是从USB读卡器或转接器去访问能看到的命令集取决于驱动实现。车机或者服务器平台UFS往往用来当启动盘或者关键数据盘固件升级流程和相关工具一般是厂商定制。不同平台间最需要注意的是LU编号和分区表映射关系。Android通常用GPT分区表直接管理通用LU而有些嵌入式系统会绕过分区表直接按固定LBA操作。换平台后如果沿用旧的上层软件很容易出容量和地址错位的事故。5. 一些踩坑后总结的实操建议先说一个我自己的习惯拿到一块UFS设备不管新板子还是返修板先完整读一遍配置描述符和各个LU的描述符把它们整理成表格放在项目文档里。这一步看着麻烦但能省下后面好几天的定位时间。毕竟设备实际呈现在系统里的样子完全取决于这些描述符里的内容。修改配置描述符之后记得一定要执行设备复位或重新上电否则新配置可能还是旧状态在起作用。我一度以为UFS不支持修改LU配置后来发现自己只是少了复位这一步。量产阶段如果每个板子都要重配LU还需要确认一遍配置写入和固化流程防止个别板子配置没保存成功就流入后续测试。不要把引导LU或者RPMB当成普通存储区随意写入。引导LU被写坏系统直接起不来RPMB密钥被覆盖安全启动和支付功能都会崩这不是通过软件能轻易修复的。尤其是RPMB一旦密钥初始化异常有些主控要换片才能恢复。所以做测试脚本时务必把这两个区域排除在随机写和压力写之外。TRIM的启用不能只看系统里有没有fstrim命令还要确认UFS驱动真的把UNMAP命令透传下去了。用systrace或者厂商日志观察能确认LBA释放请求是不是发到了设备侧。多轮压测后再看写入性能是最直接的验证方式。最后再分享一点UFS逻辑单元管理不难难的是把规范里的描述符字段和实际板子的表现对应起来。多备份几份不同厂商、不同容量UFS设备的配置描述符做对比你会发现规律非常明显。我在实际项目里就是靠这些对比快速判断出某个逻辑单元到底是容量被吃掉了还是属性配置错了。这套方法试过就知道多好用。