在移动硬盘盒上启用SSD的Trim功能

最近折腾了一小段时间的PCDN,家里刚好有一个闲置的JetsonNano和一块闲置的SSD,刚好可以跑跑PCDN,每天挣个宽带钱。具体跑的哪家,就不说了,说说在这过程中遇到的一个小问题:一般来说,PCDN或者类似的业务,对磁盘的写入压力还是比较大的,虽然可能平均的写入带宽并不高,但是也架不住每天读写的时间相当长,虽然我这块SSD是闲置的,但好歹是个传家宝,不管怎么说,还是有那么点点心疼的,肯定是不太希望哪天这SSD被写坏了。

在这种场景下,尽可能延长SSD的写入寿命就很重要了,而方法之一呢,就是想办法把SSD的Trim命令给用上。

用上Trim命令之前,可以先简单了解一下背后的逻辑,具体的可以参考Wiki,简单来说呢,因为SSD依赖垃圾回收机制来平衡NAND的磨损,但是呢具体到一整个LBA空间,只有文件系统知道哪些数据块是有效数据,所以就需要通过Trim命令,建立文件系统空闲空间和SSD底层数据块的关联,从而让SSD的主控更好的进行垃圾回收操作,一般来说,合理的使用Trim,可以有效的提高SSD的性能和寿命。当然了,Trim命令是ATA指令集里的,也就是SATA接口SSD才会有,对于SCSI以及SAS接口SSD,还有NVMe SSD来说,也有相应的UNMAPDeallocate指令,作用都是一样的。

一般来说,在Linux下,一个设备是否支持Trim操作,可以通过lsblk --discard进行查看,当输出中的DISC-GRANDISC-MAX列不为0时,说明这个设备是支持Trim操作的:

jetson-nano:chenfu:# lsblk --discard
NAME        DISC-ALN DISC-GRAN DISC-MAX DISC-ZERO
sda                0        0B       0B         0
mmcblk1            0        4M      76M         0
└─mmcblk1p1        0        4M      76M         0

比如在我这个JetsonNano上,可以看到我外接的这块SSD硬盘,对应sda设备是不支持Trim的,但是mmcblk1这个设备,也就是装系统用的一个小的MicroSD卡是支持的。

那么问题来了,针对上面的输出,sda这块盘是不支持Trim的,那怎么样才能让他支持呢?

首先需要明确的是,因为这块盘是我通过一块USB移动硬盘盒转接到板子上的,也就意味着这块硬盘并没有用原生的SATA接口(当然这块开发版本身也不支持SATA接口)。而对于移动硬盘盒而言,将SATA口转换成USB口,会需要一个桥接芯片进行协议的转换,那么桥接芯片是否支持Trim命令的转换,就显得非常重要了。对于一些老的移动硬盘盒,大多使用的是Mass Storage Class Bulk-Only Transport (BOT)这个协议,但是对于一些比较新的桥接芯片,基本都会支持一个新的叫做USB Attached SCSI Protocol (UASP) 的新协议。所以我也查了一些资料,同样也是结合产品页的一些宣传,买了一个支持UAS协议的移动硬盘盒,根据评论看,这个硬盘盒是支持Trim的,但是大部分用户似乎都是在Windows下进行测试的,在Linux下是否真的支持,是否需要新版本内核或者驱动的支持还不知道。

等硬盘盒到手,插上之后系统lsusb看了一下:

jetson-nano:~:% lsusb
Bus 002 Device 002: ID 174c:225c ASMedia Technology Inc. Ugreen Storage Device

VendorId是0x174c,也就是ASMedia公司的桥接芯片,但是225c这个ProductId并没有在USB ID数据库里查到,不过从数据库里看0x1153这个ProductId对应ASM1153这款芯片来说,那225c应该是对应着ASM225CM这个芯片?从目前的资料看,这个芯片理论上是支持Trim的,至少可以通过刷新固件来解决支持的问题。

然而系统识别出sda之后,lsblk --discard依然提示不支持Trim。

于是又搜索了一些资料,终于在Arch的SSD Wiki里找到了一些信息:

其实现在一些USB转SATA芯片(如VL715、VL716等)以及在外接NVMe硬盘盒(如IB-1817M-C31)中使用的USB转PCIe芯片(如 智微(JMicron) JMS583 )支持类似TRIM的命令。这些命令可通过 USB Attached SCSI 驱动程序(在Linux下称为”uas”)发送。然而内核可能不会自动检测到并启用这一功能。

会不会是因为芯片是支持的,但是系统默认没有开启呢?于是按Wiki里的说法,使用sg_readcap -l /dev/sda命令读取设备的标志位:

jetson-nano:chenfu:# sg_readcap -l /dev/sda
Read Capacity results:
   Protection: prot_en=0, p_type=0, p_i_exponent=0
   Logical block provisioning: lbpme=0, lbprz=0
   Last LBA=937703087 (0x37e436af), Number of logical blocks=937703088
   Logical block length=512 bytes
   Logical blocks per physical block exponent=0
   Lowest aligned LBA=0
Hence:
   Device size: 480103981056 bytes, 457862.8 MiB, 480.10 GB

发现Logical block provisioning: lbpme=0, lbprz=0其中lbpme=0,因为LBPME位为0,所以内核默认是不会开启DISCARD的支持。针对这种情况,还需要继续通过sg_vpd -a /dev/sda命令查询设备支持的命令情况:

jetson-nano:chenfu:# sg_vpd -a /dev/sda
Supported VPD pages VPD page:
  ...
Unit serial number VPD page:
  Unit serial number: 704108E11D02

Device Identification VPD page:
  Addressed logical unit:
    designator type: NAA,  code set: Binary
      0x5000000000000001

Block limits VPD page (SBC):
  Write same non-zero (WSNZ): 0
  ...

Block device characteristics VPD page (SBC):
  Non-rotating medium (e.g. solid state)
  ...
Logical block provisioning VPD page (SBC):
  Unmap command supported (LBPU): 1
  Write same (16) with unmap bit supported (LBPWS): 0
  Write same (10) with unmap bit supported (LBPWS10): 0
  Logical block provisioning read zeros (LBPRZ): 0
  Anchored LBAs supported (ANC_SUP): 0
  Threshold exponent: 0 [threshold sets not supported]
  Descriptor present (DP): 0
  Minimum percentage: 0 [not reported]
  Provisioning type: 0 (not known or fully provisioned)
  Threshold percentage: 0 [percentages not supported]

可以发现在Logical block provisioning VPD page (SBC)段下,有Unmap command supported (LBPU): 1,说明设备本身是支持Unmap指令的,因为前面说到,ATA中的Trim其实就是对应的SCSI中的UNMAP,所以支持UNMAP也就是支持了Trim,当然这中间的转换过程,应该是有硬盘盒的主控来完成。

那既然在物理上是支持Trim的,那剩下的就是逻辑上怎么启用的问题了,先看下目前内核识别的设备的provisioning_mode:

jetson-nano:chenfu:# cat /sys/block/sda/device/scsi_disk/0:0:0:0/provisioning_mode
full

可以发现输出是full,也就是说内核当前是没有检测到设备支持Trim特性,解决方法也比较简单,直接echo unmap到这个文件:

jetson-nano:chenfu:# echo unmap > /sys/block/sda/device/scsi_disk/0:0:0:0/provisioning_mode
jetson-nano:chenfu:# lsblk --discard
NAME        DISC-ALN DISC-GRAN DISC-MAX DISC-ZERO
sda                0      512B       4G         0
mmcblk1            0        4M      76M         0
└─mmcblk1p1        0        4M      76M         0

可以看到,强制指定provisioning_mode为unmap之后,lsblk --discard的输出已经提示sda设备支持Trim了。

最后,为了能让这个特性可以在插入硬盘盒的时候自动生效,可以手动编写一个Udev的规则文件:

echo 'ACTION=="add|change", ATTRS{idVendor}=="174c", ATTRS{idProduct}=="225c", SUBSYSTEM=="scsi_disk", ATTR{provisioning_mode}="unmap"' >>/etc/udev/rules.d/10-uas-discard.rules

也就是说,当有idVendor为174c,idProduct为225c的设备(也就是我的这个硬盘盒)连接的时候,自动设置provisioning_mode为unmap。