上一篇文章我们聊了闪存转换层(FTL),它的职责其实不少,而逻辑地址到物理地址的映射表(即L2P表)只是其中最基本、也是被访问最频繁的一部分。

L2P,全称是Logical to Physical,也就是逻辑地址到物理地址的映射表。那为什么非得有这张表呢?

我们先从NAND的物理结构说起。NAND闪存的组织层级从上到下依次是:通道(Channel)、芯片使能(Chip Enable,CE)、Die、Plane、Block、Page。其中Die是能独立执行命令并返回状态的最小单元。而一个CE上可以挂载多个Die,一个Channel上又可以挂载多个CE。

举个例子,当我们要往某个Page里写入数据时,必须指定一个完整的物理路径,比如:Channel 0、CE 0、Die 0、Plane 0、Block 0、Page 0。这就像写快递地址一样,得精确到“北京市某某区某某街道某某小区某号楼某单元某室”,只有这样,数据才能被准确写入。

但问题来了——直接用物理地址写数据,管理起来非常麻烦。假设我们有一个简化的NAND结构:1个Channel,1个CE,2个Die,2个Plane,10个Block,每个Block有100个Page。写入数据时,我们会从“ch0 ce0 die0 plane0 block0 page0”开始,写满一个Page(比如16KB)后,接着写page1、page2……一直到page99。这时block0满了,就得切换到block1;等block9也写满了,再切换到die1。换句话说,每写一个页面,都得判断当前block是否写满、当前die是否用完、需不需要换块或者换die。在Die数量少的时候还行,但在企业级SSD中,Die动辄上百个,这种判断就变得极其繁琐,严重影响效率。

于是,逻辑地址应运而生。逻辑地址是我们上层抽象出来的概念,比如把逻辑页从0开始连续编号,一直往上递增,没有尽头。上层只管写逻辑页0、1、2……而不需要关心它到底落在物理的哪个位置。

例如:

  • 逻辑页0,对应物理位置:ch0 ce0 die0 plane0 block0 page0;
  • 逻辑页1,对应物理位置:ch0 ce0 die0 plane0 block0 page1;
  • ……
  • 逻辑页99,对应物理位置:ch0 ce0 die0 plane0 block0 page99;
  • 逻辑页100,对应物理位置:ch0 ce0 die0 plane0 block1 page0;
  • 依此类推。

当上层应用写入逻辑页时,它只需要按照逻辑页不停的往上写,FTL就会根据L2P表,自动把它映射到正确的物理地址上。上层根本不需要关心block是不是满了、die是不是换了,这些越界和切换的判断全都由FTL在底层悄悄搞定。

所以,L2P表的核心作用,就是让上层应用可以像操作一个无限大的连续空间一样去读写数据,而不被NAND复杂的物理结构所困扰。

鉴于还是没有看懂的,我列举一个例子:

你可以把NAND闪存想象成一个快递仓库。

这个仓库里有4个大货架,编号1到4。每个货架有4层,编号1到4。每一层又有10个存放快递的槽位,编号1到10。

那么,一个快递要放在哪里,就需要一个非常精确的地址,比如:“货架2、第3层、第7个槽位”。这个地址,就是物理地址。

一开始,快递员按顺序放快递:从货架1的第1层第1个槽位开始,依次放到第10个槽位,然后换到第2层,再到第3层、第4层;等货架1放满了,再换到货架2,继续一层一层往下放。虽然要时刻留意“这一层满了没有”“这个货架满了没有”,但只要仓库不大,还能应付。

但现实中的仓库可没这么简单——快递不光要往里放,还要往外取。某个槽位的快递被取走后,就空出来了。如果快递员每次放新快递时,都要在整个仓库里东找西翻,看哪个货架、哪一层还有空位,那效率就太低了。仓库越大,这种“找空位”就越让人头疼。

于是,“逻辑地址”这个概念就派上了用场。

我们把仓库里所有可以放快递的位置,抽象成一组连续的编号——从1一直排到10000。这就是逻辑地址。

快递员不再关心快递到底放在哪个货架、哪一层、哪个槽位。他只需要知道:“这个快递要放到逻辑位置9527。” 剩下的工作,就交给一张叫L2P的映射表来处理。这张表会自动把逻辑位置9527,对应到某个具体的物理位置上——可能是货架3、第2层、第9个槽位。

这样一来,快递员再也不用操心哪里有空位、哪里满了,只需要查一下逻辑位置,系统就会帮他搞定一切。效率大大提升,管理也变得更简单。