那我问你,什么是NVMe PRP?
前不久我们介绍过NVMe的另外一种描述数据的方式,没看过的同学可以赏脸看一下:NVMe数据地址描述方式之SGL,总得来说,SGL是一种极其灵活的数据描述方式,就像一个猫系女郎,拥有傲人的身材,曼妙的身姿,可以解锁多种姿势,风姿绰约,柔情似水,让你欲罢不能。

但是,这意味着你也要掌握很多姿势,这对你的学习能力提出了很高的要求,挑灯夜读,凿壁借光,白天去宾馆学外语,晚上去酒店对台词是必不可少的,久而久之,必然因过度劳累而出错。

这就是为什么很多SSD控制器对于SGL的支持往往是有限制的,因为要覆盖SGL的所有场景,会导致系统的复杂度上升,反而会影响系统的稳定性。
而相比于SGL,今天我们要介(约)绍(会)的对象,PRP,则更像是一个大家闺秀,长相端庄,举止规矩,上得厅堂,下得厨房。
1 什么是PRP
PRP的全称是Physical Region Page,这里的page,是指计算机内存的最小管理单元,也称memory page,每个page的大小是一样的,绝大多数情况下都是默认4KiB。要传输的数据就存放在一个或多个memory page内,PRP就是一种围绕着memory page做文章的数据分布描述机制。
我们先稍微说一下Memory page size如何设置,然后开聊PRP。
1.1 如何设置Memory Page Size
显而易见的事实是,内存的memory page的size(MPS),必然是Host告诉SSD的,但是Host人还蛮好,他会先通过NVMe Controller Capabilities Register寄存器,询问SSD支持的MPS范围:

如果你的控制器只支持4KiB的MPS,那么只需要将MPSMAX和MPSMIN都设置为0即可,也就是最大和最小都是$2^{12+0}=4096$ Byte ,这样让Host无路可走,只能乖乖就范,使用4KiB MPS。
在了解了SSD所支持的MPS范围之后,Host就会通过设置NVMe Controller Configuration Register来告诉SSD需要用哪一种MPS:

需要注意的是,NVMe spec里的“MPS”是一个字段和术语,不是真实的memory page size,而本文里的MPS指代的是内存物理page的size,两者之间的关系是:本文里的MPS=2(12+Spec里的MPS)。
1.2 PRP Entry的构成
一个PRP entry其实就是一个内存地址,它的大小是8个字节,在NVMe spec里,被分成了两部分,Page Base Address和Offset:

Offset的存在跟我们前面提到的MPS有关系,其中n和MPS的关系是2(n+1) = MPS。
如果offset是0,那PRP表示的是一个memory page size(MPS)对齐的地址;如果offset不为0,PRP表示的是一个非memory page对齐的地址。
举例来说,假如MPS是4KiB,那么0xA_12345000就是一个MPS对齐的地址,而0xA_12345678就是一个非MPS对齐的地址,需要注意的是,即使是非MPS对齐的地址,也必须是dword对齐,即bit 1:0必须是0.
2 PRP的规则
那么PRP是怎么运作的呢?它的规则很简单,任何一个具有幼儿园学历的人都能轻松掌握。
首先,前面我们介绍过,host的内存是按照Memory page来管理的,而一个IO的数据可能分布在多个离散的Memory page中,那么我们就需要相应数量的地址来描述这些Memory page,而PRP要做的事情非常简单,就是要把这些地址传递给SSD。

问题来了,那怎么传递呢?
我们知道,在NVMe中,每一个IO都是16个dword,其中有4个dword用来表示数据的地址,每2个dword组成一个64位的地址,这样我们就有了2个PRP entry,分别是PRP1和PRP2。
其中,第一个memory page的地址,也就是上图中的Addr_0, 总是被放在PRP1,那么其他的address,都需要PRP2来表达,那PRP2可能有意见了:我就一个PRP entry,如何让我表达多个地址?

很简单,我们可以把其他的地址统一存放在一个单独的memory page里,然后把这个memory page的地址存放在PRP2,也就是PRP2指向这个存放了地址的memory page:

那又有好学的同学问了,那我问你,要是地址数量太多,连这个单独的memory page也存不下了,怎么办?
没事,这种情况下,memory page里所存放的最后一个地址,自动化身pointer,指向一个新的memory page,这个新的memory page里存放着剩余的地址:

依次类推,如果新的memory page还是放不下,那就继续下一个memory page。
细心的网友可能发现了,这个过程涉及了如何计算一个IO所需要的PRP entry数量,没问题,这个也比较简单。
2.1 如何计算一个IO需要多少个PRP entry
一个IO需要的PRP entry数量:CEILING_DIV((PRP1 & (MPS - 1) + IO_SIZE) / MPS,也就是PRP1的offset,加上IO的size,除以一个page的大小,然后对结果向上取整。
比如,假设MPS是4KiB,PRP1是0xA_12345678,一个IO的size是128KiB,那么它需要的PRP entry数量是:(0xA_12345678 & 0x00000FFF + 0x20000) / 0x1000, 向上取整为33,也就是需要33个PRP entry。
知道了一个IO需要多少个PRP entry,PRP2的作用也分为三种情况:
- 如果只需要一个PRP entry,那么PRP2未使用;
- 如果需要2个PRP entry,那么PRP2就是一个PRP entry;
- 如果需要2个以上PRP entry,那么PRP2就是一个PRP list pointer。
2.2 PRP对齐规则
有些同学可能对于NVMe里的PRP对齐规则比较confused,其实,只需要记住下面两条即可:
- 整个IO comamnd内,只有第一个PRP entry和第一个PRP list pointer允许非MPS对齐;
- PRP entry必须dword对齐, PRP list pointer必须qword对齐;
很显然,由于第一个PRP entry总是在command里的PRP1,所以PRP1是允许非MPS对齐的,而PRP2呢,当它是PRP list pointer时,它就是整个command里第一个PRP list pointer,所以可以非MPS对齐,否则,需要MPS对齐。
3 一个例子
这是一个真实的read command的trace,从图中我们可以看到:
- Command要传输的数据size是40KiB (10 LBAs * 4 KiB / LBA = 40KiB);
- PRP1=0x1_049E0000, 很明显是4KiB对齐的;
- 因此整个command需要的PRP entry的数量是:(0x1_045E0000&0xFFF+40KiB) / 4KiB = 10,也就是说除了PRP1之外,还需要9个PRP entry;
- 所以PRP2必然是一个PRP list pointer,它所指向的memory page里存放着9个合法的PRP entry;
- PRP2=0x1_03A23100,可以看到SSD从这个地址获取了9个PRP entry(trace里获取了12个,但真实有效的只有9个,这是允许的);
- 接下来就是数据传输的时间,可以看到SSD先往PRP1传输了4KiB,然后往其他的PRP entry里分别传输了4KiB,总共40KiB
- 最后,Post CQ entry给Host,通知Host命令已经完成。
最后
🍅如果您觉得有帮助,欢迎点击文章下方的“打赏”请博主和西红柿汤~
🤝如需转载,请注明出处
😄欢迎关注我的公众号: