SSD部分
目前维护的版本是76a9d8e333f081a9df30b5f8706e9be81517665c
,"Update ZNS support statements"
数据结构
ssd
变量名
变量类型
说明
ssdname
char*
rmap
uint64_t *
反向映射表,assume it's stored in OOB
dataplane_started_ptr
bool*
ssdparams
对闪存的配置,例如sector的大小、每个page的sector数、每个block的页数等等。(闪存组织结构是 channel->lun->plane->block->page->sector)
延迟:page读/写延迟、块的擦除延迟以及channel的page传输延迟
一些计算得到的值
ssd_channel
变量名
变量类型
说明
nluns
int
next_ch_avail_time
uint64_t
初始化为0
busy
bool
初始化为0
gc_endtime
uint64_t
?这个变量没有初始化
nand_lun
变量名
类型
说明
pl
struct nand_plane *
plane级的链表
npls
int
根据用户参数初始化,每个lun的plane数
next_lun_avail_time
uint64_t
初始化为0
busy
bool
初始化为false
gc_endtime
uint64_t
?未初始化
nand_plane
变量名
类型
说明
nblks
int
根据用户参数初始化,每个plane的block数
blk
struct nand_block*
block级的链表
nand_block
变量名
类型
说明
pg
struct nand_page*
page层的指针
npgs
int
根据用户参数进行初始化,就是每个block的页数量
ipc
int
invalid page数量,初始化为0
vpc
int
valid page数量,初始化为0
erase_cnt
int
初始化为0,初始化为0
wp
int
?写指针,初始化为0
nand_page
变量名
类型
说明
sec
int *
记录page内每个sector的状态,都初始化为SEC_FREE(0)
nsecs
int
每个page的sector个数
status
int
page的状态,初始化为PG_FREE(0)
ppa
这里用到了联合
,将64位的物理页地址按字段进行划分。
起始不用在意这些ppa的各个字段怎么区分,因为这个版本的femu里不是直接利用ppa去写flash的,而是绕了一步:
利用写指针
write_pointer
来实现channel、lun、plane、block、page的改变;将对应的字段赋值给ppa
在建立反向映射表
rmap
的时候,调用了ppa2pgidx
函数,利用如下公式将ppa
转化为一个page index
write_pointer
变量名
类型
说明
curline
struct line *
ch
int
初始化为0
lun
int
初始化为0
pg
int
初始化为0
blk
int
初始化为0
pl
int
初始化为0
line_mgmt
这个结构体存在于ssd
结构体中,用于ssd中line(也就是superblock)的管理;
变量名
类型
说明
free_line_list
QTAILQ_HEAD
victim_line_pq
pqueue_t *
应该是用优先队列管理的victim line
队列
full_line_list
QTAILQ_HEAD
tt_lines
int
ssd的line总数
free_line_cnt
int
空闲计数器
victim_line_cnt
int
victim line计数器
full_line_cnt
int
full line计数器
rte_ring
这个应该是借鉴了dpdk
,实现了一个无锁队列。具体操作见rte环形队列
line
line就是一个superblock,其结构比较简单:
变量名
类型
说明
id
int
与block id一样
ipc
int
这个line的invalid page 数量
vpc
int
这个line的valid page数量
entry
QTAILQ_ENTRY(line)
pos
size_t
然后用QTAILQ_ENTRY
将这些line串在某些队列里面。(必在{free,victim,full}其中之一)
NvmeRequest
变量名
类型
说明
sq
struct NvmeSQueue*
cq
struct NvmeCQueue*
ns
struct NvmeNamespace*
status
uint16_t
slba
uint64_t
起始逻辑扇区号
nlb
uint16_t
请求长度(sector)
TailQ链表相关的定义
TailQ
似乎是原本就提供的一个数据结构(在linux中叫做list_head),其示意图如下:

也就是一个双向的环形链表,这个链表提供了很多的API供使用:
QTailQLink
这个变量是整个QList的基础,利用QTailQlink
可以创建一个节点,结点包含: 1. 一个void*
类型的指针,指向下一个节点; 2. 一个struct QTailQLink
,指向前一个结点。
这一点其实让我有些费解,这样区分两个指针类型有什么深意吗?
QTAILQ_ENTRY(type)
rte_ring环形队列
ring的特点:
无锁出/入队
多消费者/生产者同时出入对
rte_ring_init
rte_ring_create
其中type有如下三种类型,当前创建的时候传入的是第二种FEMU_RING_TYPE_MP_SC
,即多个生产者、单一消费者
rte_ring_dump
rte_ring_free
rte_ring_get_memsize
基本流程
请求队列的创建与管理
femu的相关配置基本都是利用一个名为
FemuCtrl
的结构体控制的
请求队列的创建
(femu.c 189: femu_create_nvme_poller)
在这个函数调用的时候会传入一个FemuCtrl
指针,变量名为n
。根据n
中num_poller
的值,将
SSD的初始化
ssd_init
调用ssd_init
函数,传入一个参数FemuCtrl n
,这个参数里包含有一个指向ssd的指针n->ssd
,之后根据ssd里的sp
参数,进行相关配置。
初始化channel等结构体;
初始化映射表,所有LPN都指向
UNMAPPED_PPA
,也就是(~(0ULL))初始化反向映射表,所有的PPN都指向
INVALID_LPN
,也就是(~(0ULL))初始化
ssd->lm
,这里的line相当于plane级别的superblock,。因此这就就是调用ssd_init_lines函数来初始化superblock。调用ssd_init_write_pointer来初始化写指针。写指针的移动方式决定了page的写入顺序;
6.调用qemu_thread_create函数来创建
ftl_thread
线程。
ssd_init_lines
使用
g_malloc
库函数为每个line结构体分配空间,也就是分配了spp->tt_lines(total lines)个line结构体的空间;使用QTAILTQ_INIT函数分别初始化
free_line_list
和full_line_list
这两个TIALQ链表;调用
pqueue_init
函数初始化lm->victim_line_pq
。这个函数应该是传入了几个函数指针,用来设置、比较、获取优先级,获取、设置位置。循环设置每个line的id等参数:每个line的id依次递增;ipc、vpc都等于0。同时初始化
lm->free_line_cnt
ssd_init_write_pointer
使用
QTAILQ_FIRST
函数来获取lm->free_line_list
中的第一个元素,赋值给变量ssd->wp->curline
;使用
QTAILQ_REMOVE
函数,从lm的free_line_list中移除第一个元素,并维护计数器;初始化当前
wpp
的ch
,lun
,pg
,blk
和pl
几个值。
qemu_thread_create
这个函数需要传入4个
参数:
Thread指针
一个字符串,也就是线程的名字
一个函数指针,名为
start_routine
void*类型的arg
int类型的mode
线程创建函数的具体细节待日后补充
ssd写流程,ssd_write
这个函数有两个参数:ssd
和req
。其中req中包含了:① 请求的起始地址,req->slba
;② 请求的长度,req->nlb
写的流程如下:
根据据
req
的信息和ssd的配置来计算起始页号和结束页号;调用should_gc_high判断是否需要垃圾回收。如果需要就调用do_gc函数进行垃圾回收;
MARK 这里是一个待实现的功能,即cache。
判断当前lpn对应的ppa(未写入的情况下会是UNMAPPED_PPA)是否是一个已经有效的地址。如果是的话:① 旧的ppn置无效,调用
mark_page_invalid
;② 更新反向映射,调用函数set_rmap_ent
申请新的ppa,调用
get_new_page
更新映射表
set_maptbl_ent
、反向映射表set_rmap_ent
、页状态mark_page_valid
、写指针(ssd_advance_write_pointer);最后生成一个
nand_cmd
,type设置为USER_IO
,cmd是NAND_WRITE
,time为req->stime
,并调用ssd_advance_status
来更新时间线。
ssd读流程,ssd_read
参数、请求的处理与写流程类似,最后调用ssd_advance_status来更新时间线。
垃圾回收
should_gc_high
判断free_line_cnt
与ssd->sp.gc_thres_lines_high
的大小关系,当free line的数量小于阈值时触发GC。
do_gc
这个函数有两个参数,第一个是ssd
,第二个是一个布尔类型的force
。如果force是true,就会正常进行垃圾回收;而如果force是false,那么仅仅在line的invalid页数达到1/8以上的时候才会进行垃圾回收。
用户的写操作触发GC的时候,会传入true
;而ftl线程后台执行GC的时候会传入false
。
FTL时间线的维护
FTL里读写对于时间线的推进,采用的都是一个函数,即ssd_advance_status
这个函数针对读、写、擦三个请求分别进行了时间线的维护。这个维护是以lun(也就是die)为粒度的。时间线本身的维护也很简单,仅仅是通过一个uint64_t
类型的变量next_lun_avail_time
来实现的。
读请求的时间推进
判断①请求的下发时间;②lun下个一可用状态的时间 的大小关系,并取其中较大作为请求开始执行的时间;
将lun的可用时间调整为:请求的开始执行时间后推一个page read时延;
请求的latancy为 lun的下一个可用时间-请求的下发时间。
写请求的时间推进
判断①请求的下发时间;②lun下个一可用状态的时间 的大小关系,并取其中较大作为请求开始执行的时间;
将lun的可用时间调整为:请求的开始执行时间后推一个page write时延;
请求的latancy为 lun的下一个可用时间-请求的下发时间。
块擦除的时间推进
判断①请求的下发时间;②lun下个一可用状态的时间 的大小关系,并取其中较大作为请求开始执行的时间;
将lun的可用时间调整为:请求的开始执行时间后推一个block erase时延;
请求的latancy为 lun的下一个可用时间-请求的下发时间。
最终函数的返回值为请求的时延。
用户手册
使用gdb进行debug
其中file=/home/nvm/images/u14s.qcow2
根据自己的镜像文件路径进行修改
逻辑空间与物理空间的配置
逻辑空间配置
在运行脚本,如run-blackbox.sh
中进行配置,例如配置8GB的逻辑空间:
物理空间配置
在hw/block/femu/ftl/ftl.c
源文件中,修改函数static void ssd_init_params(struct ssdparams *spp)
SSD的物理空间容量由上述几个参数相乘得到,单位为BYTE。值得注意的是,pls_per_lun
参数默认只能为1,如果修改为其他值,在别的地方会报错。例如在static void ssd_advance_write_pointer(struct ssd *ssd)
函数中:
以及在static struct ppa get_new_page(struct ssd *ssd)
函数中:
时延的配置
NAND的时延
在femu/bbssd/ftl.h
头文件中,
Last updated
Was this helpful?