前言
Disksim3.0上安装的Flashsim,能够进行SSD模拟仿真,Flashsim上的FTL层算法可供选择的有DFTL算法,FTL(纯页级映射),和FAST(fully associative sector Translation)混合FTL映射算法。因为最近的缓冲区仿真需要知道底层的FTL全合并的开销,对FAST的源码进行了阅读和理解,进行以下的总结。
FAST的算法基础
在理解代码前。首先需要理解FAST算法的机理和流程。FAST算法是源自这篇ACM会议的论文
《A Log Buffer-Based Flash Translation Layer Using Fully-Associative Sector Translation》
。这篇论文对BAST的混合FTL映射算法进行了分析和改进,指出针对随机写请求的时候,BAST算法性能表现不佳,因为日志块的空间利用率不高会出现块抖动的现象。提出将日志块划分为RW(随机写)日志块和SW(顺序写)日志块。所谓的SW日志块秉承了BAST算法数据块和日志块一对一的关系,映射实现也与BAST算法一致。而RW(随机写)日志块,采用了全关联映射的关系,即任何数据块的更新数据页都可以写在该日志块上(但这在垃圾回收无效块的时候,会产生巨大的全合并开销)。
FAST算法将日志块划分为SW块和RW块,SW块是存储顺序写请求更新,FAST对写入SW块有以下两个规则:
逻辑页地址LPN(or 逻辑扇区地址LSN)模掉一个块包含的页数(扇区数)为0,这样的写请求地址为其分配新的SW块,并将该请求的数据项写在该SW块第一个空白页上。
如果上一个LPN请求更新写入到对应的SW上,当前请求的LPN为上一个请求LPN地址相邻的位置(即LPN’=LPN+1),才将该请求写入到对应的SW位置的后一位上。
下面是截取上述论文针对FAST算法的举例说明:
这里假设一个块包含4个页,SW日志块的数量为1,RW日志块的数量为2。FAST会依次选择不同的日志块来处理,下面分五种情况来讨论,与上文类似,数字表示逻辑页号。(1)当请求4到来时,由于其满足写入SW日志块的条件(逻辑页号在块中的偏移量为0),但SW日志块非空,故需要垃圾回收操作,以产生空白的SW日志块。(2)当请求4、5依次到来时,请求4已由上述分析可知,满足写入SW日志块的条件,并构建了新的SW日志块。当请求5到来时,其满足写入SW日志块的第二个条件,故可以直接写入SW日志块的相应位置。(3)当请求4、6依次到来时,同样,请求4写入SW日志块,但请求6不满足写入SW日志块的两个条件,故通过SW日志块的全合并,将请求6直接写入新的数据块,并产生空的SW日志块。(4)当请求4、5、5依次到来时,请求4和第一个请求5依次写入SW日志块,但第二个请求5已不满足写入SW日志块的条件,故通过全合并,将第二个请求5直接写入新的数据块,并产生空的SW日志块。(5)当请求6到来时,由于不满足写入SW日志块的条件,故按顺序写入RW日志块的第一个空白页中。
FAST算法的垃圾回收算法较为复杂,在此之前需要先理解SSD中的垃圾回收数据块中存在的3种回收方式:全合并,部分合并,交换合并的概念。因为SSD不支持同步更新,数据需要异地更新,多次异地更新以后,会产生带有很多标记为无效的数据页的数据块,为了产生可用的数据块,SSD的底层垃圾回收算法会回收这些数据块,进行有效数据页备份和整块擦除,产生新的空白数据块供上层使用。所谓的全合并,部分合并,交换合并是因为采用混合FTL算法,导致有效数据页在不同回收的块中分布,采用不同的有效数据页备份和块擦除操作。具体操作如下图所示:
因为数据块和日志块中的数据页分布分散难以整理成有序的数据块存储,因此另外寻找一个新的数据块,依次读入日志块和数据块中的有效数据成为新的数据块,并回收擦除旧的数据块和空闲块,这种回收方式的开销是最大的。
第二种是最理想的回收方式,如果存在大量的顺序写更新,日志块中的数据页被顺序写入,且原来的数据块被标记为无效,因此只需要将日志块标记为数据块,擦除旧的数据块回收即可。块擦除次数仅为一次。
第三种回收方式是从数据块中顺序读出有效数据页写入日志块中,并将日志块标记为新的数据块,擦除旧的数据块为空白块。
理解了上述的三种垃圾回收方式,结合FAST算法的SW和RW块的写入操作,我们可以分析SW块可以实现理想的交换合并和部分合并,而RW块只能用全合并进行垃圾回收。继续以上述第一幅图针对FAST写入机制的举例说明进行垃圾回收的理解:
第(1)种情况对应SW日志块的部分合并,将日志块作为新的数据块,并将旧数据块中的页14、15复制到新数据块中,再擦除旧数据块,并分配一个空白块作为新的SW日志块;(3)(4)对应SW日志块的全合并,将日志块和数据块中的数据复制到新的数据块中,然后擦除两个“脏”(dirty)数据块,并为SW日志块分配一个新的空白块。RW日志块的垃圾回收情况由于FAST的全关联策略,而相对复杂。当日志块用尽时,FAST会启动垃圾回收机制,选择一个合适的日志块作为受害块,由于每个日志块可能与多个数据块关联,因此需要擦除N+1个(N为与该日志块关联的数据块个数)块,并分配N个空白块来存储各块的数据,以及一个新的日志块。这过程需要大量的读取、写入数据和擦除操作,产生了巨大的开销,而且只产生一个日志块。同时,若上述各数据块中的更新数据不在在同一个日志块中,还需从其他日志块复制有效数据到新的数据块中,并标记该日志块中相应的更新数据为无效。在最坏情况下,FAST会频繁的进行日志块的回收操作而无法顾及新的写入操作。
上述就是FAST算法的基础。下面就对仿真器的源码进行讲解。
FAST源码理解
在Flashsim上关于实现fast算法的在fast.c中实现,部分定义关键变量在fast.h中,其中会调用底层nand的oob读取和nand的读写操作,这些相关函数在flash.c有定义。
Flashsim中针对不同FTL算法的实现都需要做到四个函数操作,初始化,结束函数,读取FTL操作函数,写入FTL操作函数。在fast.c中依次是lm_init()
lm_end()
lm_read()
lm_write
函数lm_init()
函数主要初始化算法需要用到的内存分配和初始化,主要是完成日志块结构体数组logblk和块级映射数据表BMT,日志块中的页级映射表数组PMT的内存分配和初始化。源码如下:
1 | int lm_init(blk_t blk_num, blk_t extra_num) |
关于输入函数blk_num是数据块的个数,extra_num是日志块的个数。其中SW的个数为1。BMT是个表示块级映射的数组。
1 | BMT[lbn]=pbn //其中下标lbn是逻辑块地址,pbn是实际底层nand数组的标识序号也是我们理解的物理块地址 |
针对日志块的页级映射关系如何实现,需要对结构体LogMap进行解读
1 | /* Log blocks are composed of ONE sequential log block |
该结构体中包含了该日志块中当前可用的空白页数fpc,该日志块对应的底层nand数组的标识(也就是我们理解的物理块,即哪个(pbn)物理块是当日志块在使用),lpn[PAGE_NUM_PER_BLK]
是当前的日志块中每个数据页位置上存放的对应的lpn(不存放数据,则初始化为-1),这个lpn存放的并不是有效的,只有结合下面的标识符lpn_status[PAGE_NUM_PER_BLK]
才能判断该lpn是否有效(-1无效,0空闲,1有效)。
简单的RW块可以用上述的结构体LogMap进行表述了,但是SW需要实现日志块和数据块一对一的关系表示,因此在此结构进行扩展为seq_log_blk:
1 | // This is only for ONE sequential log block |
其中data_blk表示对应的数据块(即数据块的物理地址,底层nand数组的下标)
1 | 一些其他相关的全局变量在文件的头部定义如下: |
其中PMT这个日志块数组中PMT[0]永远是SW块,其他是RW块。
针对FAST的释放函数,就是完成对应初始化函数内存释放,源码如下:
1 | void lm_end() |
FAST的读操作实现进行源码实现比较简单:
1 | size_t lm_read(sect_t lsn, sect_t size, int mapdir_flag) |
1 | 上述函数完成的可以理解为: |
FAST的写操作就较为复杂了,写入有一个写入冲突,即如果写入更新的数据,要将数据块中的数据项标记为无效,将新的数据项写入到对应的RW、SW中。但是同时需要注意的是更新的数据可能还会存在上一次日志块中,因此需要将其置为无效。
1 | size_t lm_write(sect_t lsn, sect_t size, int mapdir_flag) |
关于写入日志块操作函数writeToLogBlock(lsn,lbn,lpn)
源码就相当的冗余。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84其函数的代码流程可以简述为:
首先查看是否分配了SW块,即SW块对应的实际物理块
if(global_SW_blk.logblk.pbn == -1 ) {
//如果未分配实际的物理块,则寻找对应的空闲块进行绑定
global_SW_blk.logblk.pbn = nand_get_free_blk(1);
//以下是lopMap的结构体初始化
global_SW_blk.logblk.fpc = PAGE_NUM_PER_BLK;
global_SW_blk.data_blk = -1;
for( i = 0; i < PAGE_NUM_PER_BLK; i++) {
global_SW_blk.logblk.lpn[i] = -1; // -1: no data written
global_SW_blk.logblk.lpn_status[i] = 0; // 0: free
}
}
//再绑定PMT日志块数组标定
PMT[0] = global_SW_blk.logblk;
之后所有重新分配新的SW块都是进行上述同样的操作。
下面遵循FAST写入SW块的原则,首先判断偏移量offset是否为0,如果是,则直接将其写入SW块的第一个数据页位置,这里又得考虑当前的SW块是否为空,如果不为空,则需要将这个SW块进行部分(交换)合并操作(根据当前的SW的块大小进行合并操作),并未新的lsn分配新的SW块。代码片段如下:
if ( global_SW_blk.logblk.fpc == PAGE_NUM_PER_BLK) { // SW logblock is empty
//directly do write on SW block because data block has been written already !
ASSERT(global_SW_blk.data_blk == -1);
}
else {
// completely sequentially written -> switch merge
if(global_SW_blk.logblk.fpc == 0) { // no free pages in SW_BLk
merge_switch(global_SW_blk.logblk.pbn, global_SW_blk.data_blk);
merge_switch_num++;
}
// partially sequentially written -> partial merge
else {
merge_partial(global_SW_blk.logblk.pbn, global_SW_blk.data_blk, global_SW_blk.logblk.fpc,lpn*SECT_NUM_PER_PAGE);
merge_partial_num++;
}
//allocate new SW_blk and initialize it
global_SW_blk.logblk.pbn = nand_get_free_blk(1);
global_SW_blk.logblk.fpc = PAGE_NUM_PER_BLK;
global_SW_blk.data_blk = -1;
for( i = 0; i < PAGE_NUM_PER_BLK; i++) {
global_SW_blk.logblk.lpn[i] = -1; // -1: no data written
global_SW_blk.logblk.lpn_status[i] = 0; // 0: free
}
PMT[0] = global_SW_blk.logblk; // insert new SW_blk info into PMT
}
ASSERT(BMT[lbn] != -1);
//更新数据前,先将原数据块中的数据页置为无效(底层nand都是以扇区为操作单位,但是FTL是页操作单位,这个转化在整段代码中都需要注意的)
// invalidate page in data block
s_psn = ((BMT[lbn] * PAGE_NUM_PER_BLK) * SECT_NUM_PER_PAGE);
s_lsn = lpn * SECT_NUM_PER_PAGE;
for(i = 0; i<SECT_NUM_PER_PAGE; i++){
nand_invalidate(s_psn + i, s_lsn + i);
}
nand_stat(OOB_WRITE);
// write page in SW_blk
//global_SW_blk.logblk.pbn是这个SW对应真实的物理块号pbn
pbn = global_SW_blk.logblk.pbn;
//global_SW_blk.data_blk是这个SW对应的数据块的物理块号,和逻辑块号lbn存在对应的关系
global_SW_blk.data_blk = BMT[lbn];
global_SW_blk.logblk.fpc--;
global_SW_blk.logblk.lpn[page_offset] = lpn; //store lpn of the request
global_SW_blk.logblk.lpn_status[page_offset] = 1; // 1: valid
PMT[0] = global_SW_blk.logblk;
//重新更新s_psn的地址(因为新的地址位置更新在新的SW块上了)
s_psn = SECTOR(pbn, 0);
s_lsn = lpn * SECT_NUM_PER_PAGE;
memset (lsns, 0xFF, sizeof (lsns));
for (i = 0; i < SECT_NUM_PER_PAGE; i++)
{
lsns[i] = s_lsn + i;
}
//nand_page_write 需要写入的物理扇区地址,逻辑扇区号(一组地址)
nand_page_write(s_psn, lsns, 0, 1);
但是如果偏移量offset不为0,则需要判断这个偏移量是否紧接着SW块上的下一个空闲位置。这里首先需要判断当前唯一的SW块是否是更新数据块对应的SW块,如果不是,则直接可以判断不是连续的,将该请求更新到RW块即可,如果是对应的SW块,查看该LPN是否紧挨着上一个请求的地址,如果是,那好极了直接插入SW块,如果不是,则需要判断这个请求地址是在之前的还是在更新数据后面的(会采用不同的合并策略)。先看对应的SW块是否为当前数据块的对应SW部分代码:
1 | // when lbn is the "owner" of SW log block |
如果SW不是对应当前的数据块,且offset又不是0,肯定可以判读就是随机写了,这就找RW块上空闲的位置写入,首先判读原来的数据是否存在旧的数据块还是RW上,先判断是否在RW块,在则置该数据为无效,不在则找对应旧数据块上的数据为无效。之后利用getRWblk()找到当前可用的RW日志块,写到对应的空闲位置上1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80if(BMT[lbn] == global_SW_blk.data_blk){
..........
}else{
// invalidate page in log block if some data written in some log block
invalid_flag = 0;
for( i = 1; (i < total_log_blk_num) && (invalid_flag != 1); i++){
for( j = 0; j < PAGE_NUM_PER_BLK; j++){
if((PMT[i].lpn[j] == lpn) && (PMT[i].lpn_status[j] == 1)) {
// invalidate
PMT[i].lpn_status[j] = -1; // -1: invalid
s_psn = ((PMT[i].pbn * PAGE_NUM_PER_BLK + j) * SECT_NUM_PER_PAGE);
s_lsn = lpn * SECT_NUM_PER_PAGE;
for(k = 0; k<SECT_NUM_PER_PAGE; k++){
nand_invalidate(s_psn + k, s_lsn + k);
}
nand_stat(OOB_WRITE);
invalid_flag = 1;
break;
}
}
}
if(invalid_flag == 0 ) {
// invalidate page in data block
s_psn = ((BMT[lbn] * PAGE_NUM_PER_BLK + page_offset) * SECT_NUM_PER_PAGE);
s_lsn = lpn * SECT_NUM_PER_PAGE;
for(i = 0; i<SECT_NUM_PER_PAGE; i++){
nand_invalidate(s_psn + i, s_lsn + i);
}
nand_stat(OOB_WRITE);
}
currRWblk = getRWblk();
//这里的RW块的回收是被动的,当RW不够用的时候,回收FirstRWblk(这里就涉及到了全合并操作)
// no available RW log block
if(currRWblk == -1){
//这里的firstRWblk是1-total_log_num-1的取值,就是PMT[]的下标
firstRWblk = getFirstRWblk();
//这个merge_full是专门回收RW日志块的,找到PMT[x]---->logMap就有了
merge_full(firstRWblk);
//全合并完,重新为该下标PMT[x]分配绑定新的物理块和初始化
//initialize
PMT[firstRWblk].pbn = nand_get_free_blk(1);
PMT[firstRWblk].fpc = PAGE_NUM_PER_BLK;
memset(PMT[firstRWblk].lpn, 0xFF, sizeof(int)*PAGE_NUM_PER_BLK);
memset(PMT[firstRWblk].lpn_status, 0x00, sizeof(int)*PAGE_NUM_PER_BLK);
//初始化后就当新的块来使用
global_currRWblk = firstRWblk;
currRWblk = firstRWblk;
}
//如果当前的currRW有空的位置,则写入空闲位置上
currRWpageoffset = PAGE_NUM_PER_BLK - PMT[currRWblk].fpc;
// write page
pbn = PMT[currRWblk].pbn;
s_psn = SECTOR(pbn, currRWpageoffset * SECT_NUM_PER_PAGE );
s_lsn = lpn * SECT_NUM_PER_PAGE;
PMT[currRWblk].lpn[currRWpageoffset] = lpn;
PMT[currRWblk].lpn_status[currRWpageoffset] = 1; // 1: valid
memset (lsns, 0xFF, sizeof (lsns));
for (i = 0; i < SECT_NUM_PER_PAGE; i++)
{
lsns[i] = s_lsn + i;
}
nand_page_write(s_psn, lsns, 0, 1);
PMT[currRWblk].fpc--;
}
}
合并操作代码
- SW的交换合并(
void merge_partial(int log_pbn, int data_pbn, int fpc, int req_lsn)
) - SW的部分合并(
void merge_partial(int log_pbn, int data_pbn, int fpc, int req_lsn)
) - SW的全合并(
void merge_full_SW(int req_lsn)
) - RW的全合并(
void merge_full(int pmt_index)
)
需要注意的是SW块的全合并只是对应一个绑定的数据块,只涉及2次块擦除和寻找一个新的空闲块备份数据,和RW的全合并是完全不同的。RW因为全关联,所以是不存在交换和部分合并的操作的!!!。先从简单的交换合并代码开始理解:
1 | // for sequential |
SW交换合并只需要将SW标记为新的数据块就行,但是这里注意要更新对应的块映射(BMT[?]!!!),同时擦除旧的数据块。
SW的部分合并,就需要从旧的数据块中读出有效的数据,依次添入SW后面的空闲位置,同时将SW标记为新的数据块,更新对应的块映射(BMT[?]!!!),擦除旧的数据块
1 | void merge_partial(int log_pbn, int data_pbn, int fpc, int req_lsn) |
SW块全合并,其实和SW的交换合并很像,但是需要找到一个全新空块的数据块,将SW中有效的数据页和旧数据块(也可能在其他RW块上)中的数据页读出来
如1,2,3,2这样引发的全合并(如果块大小为4),则SW的1,3为有效,req_lpn=2为最新的,从其他地方(旧数据块中或RW)找到4,一起写到新的块。
1 | void merge_full_SW(int req_lsn) |
RW块的全合并操作就相当复杂了。就是要找这个RW到底对应了多少个数据块。
- 找到对应的数据块data_pbn
logMap.lpn[x]项存的lpn满足logMap.lpn[x].status=1时,则可以将用该lpn计算—–>lbn—BMT[lbn]–>data_pbn
统计不同的数据data_pbn就可以知道关联多少不同的数据块 - 针对不同的
data_pbn
分配新的物理块地址new_pbn
- 从data_pbn<---->lbn—>lpn,依次找到最新的数据页写入到新的
new_pbn
上
瞎话讲那么多还是,先看代码讲下来:
关于输入的pmt_index是PMT[?]的下标---->1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164void merge_full(int pmt_index)
{
int i,j,k,m,h;
int size;
int old_pbn;
int lbn,lpn,new_pbn,pbn,offset, invalid_flag;
int s_lsn, s_psn;
sect_t lsns[SECT_NUM_PER_PAGE];
//这个错误判断,首先回收的RW块必须是耗净的,且不能回收PMT[0](专门映射SW块的)
if(PMT[pmt_index].fpc != 0 && pmt_index == 0) {
printf("something sucks");
ASSERT(0);
}
// Check with all page in a log block
//依次查找当前要回收的RW中的有效页`PMT[pmt_index].lpn_status[i] == 1`
for(i = 0; i<PAGE_NUM_PER_BLK; i++)
{
if(PMT[pmt_index].lpn_status[i] != 1){ // -1: invalid, 0: free, 1: valid
continue;
}
else{
offset = PMT[pmt_index].lpn[i] % PAGE_NUM_PER_BLK;
//依据lpn--->lbn--BMT-->old_pbn(但是这个数据块可能同时还关联了一个SW块这需要处理)
lbn = PMT[pmt_index].lpn[i] / PAGE_NUM_PER_BLK;
old_pbn = BMT[lbn];
//如果还关联一个SW块,则先对该old_pbn块进行一次SW的部分合并
if(old_pbn == global_SW_blk.data_blk) {
merge_partial(global_SW_blk.logblk.pbn, global_SW_blk.data_blk, global_SW_blk.logblk.fpc,-1);
merge_partial_num++;
global_SW_blk.logblk.pbn = nand_get_free_blk(1);
global_SW_blk.logblk.fpc = PAGE_NUM_PER_BLK;
global_SW_blk.data_blk = -1;
for( h = 0; h < PAGE_NUM_PER_BLK; h++) {
global_SW_blk.logblk.lpn[h] = -1; // -1: no data written
global_SW_blk.logblk.lpn_status[h] = 0; // 0: free
}
PMT[0] = global_SW_blk.logblk; // insert new SW_blk info into PMT
continue;
}
//OK,经过上述处理以后,关联的旧数据块不可能出现最新数据在SW上,可以开始分配新的数据块,开始数据整合
new_pbn = nand_get_free_blk(1);
BMT[lbn] = new_pbn;
merge_full_num++;
//对该lbn所属于的lpn全部遍历重写入新的块
for(j =0 ; j < PAGE_NUM_PER_BLK ; j++) {
lpn = (lbn * PAGE_NUM_PER_BLK) + j;
/* for nand_oob_read */
//s_psn是旧数据块相同偏移位置位置上,物理扇区地址,下面读取该页数据是否要使用
s_psn = SECTOR(old_pbn, j*SECT_NUM_PER_PAGE); // chk if correct
s_lsn = lpn * SECT_NUM_PER_PAGE;
memset (lsns, 0xFF, sizeof (lsns));
for (h = 0; h < SECT_NUM_PER_PAGE; h++) {
lsns[h] = s_lsn + h;
}
size = nand_oob_read(s_psn);
//旧的数据块中的psn为有效,读取写入到新的块地址,将其旧地址置为无效
if(size == 1) // valid -> invalidate page in the data block
{
// invalidate page in data block
s_psn = SECTOR(old_pbn, j*SECT_NUM_PER_PAGE); // chk if correct
s_lsn = lpn * SECT_NUM_PER_PAGE;
// read from data block - youkim
memset (lsns, 0xFF, sizeof (lsns));
for (h = 0; h< SECT_NUM_PER_PAGE; h++)
{
lsns[h] = s_lsn + h;
}
nand_page_read(s_psn, lsns, 1);
// invalidate page in data block
for(h = 0; h<SECT_NUM_PER_PAGE; h++){
nand_invalidate(s_psn + h, s_lsn + h);
}
nand_stat(OOB_WRITE);
// write into new pbn
//此时s_psn为新块的写入物理扇区地址
s_psn = SECTOR(new_pbn,j* SECT_NUM_PER_PAGE);
s_lsn = lpn * SECT_NUM_PER_PAGE;
memset (lsns, 0xFF, sizeof (lsns));
for (h = 0; h< SECT_NUM_PER_PAGE; h++)
{
lsns[h] = s_lsn + h;
}
nand_page_write(s_psn, lsns, 1, 1);
}
else if(size == -1)
{
//如果旧的数据块中数据页为无效,则遍历RW日志块找到其中最新数据
invalid_flag = 0;
for( k = 1; (k < total_log_blk_num) && (invalid_flag != 1); k++){
for( m = 0; m < PAGE_NUM_PER_BLK; m++){
if((PMT[k].lpn[m] == lpn) && (PMT[k].lpn_status[m] == 1)) {
// invalidate page in log block
PMT[k].lpn_status[m] = -1; // -1: invalid
s_psn = ((PMT[k].pbn * PAGE_NUM_PER_BLK + m) * SECT_NUM_PER_PAGE);
s_lsn = lpn * SECT_NUM_PER_PAGE;
// read from data block - youkim
memset (lsns, 0xFF, sizeof (lsns));
for (h = 0; h< SECT_NUM_PER_PAGE; h++)
{
lsns[h] = s_lsn + h;
}
nand_page_read(s_psn, lsns, 1);
// invalidate
for(h = 0; h<SECT_NUM_PER_PAGE; h++){
nand_invalidate(s_psn + h, s_lsn + h);
}
nand_stat(OOB_WRITE);
invalid_flag = 1;
break;
}
}
}
// write into new pbn
s_psn = SECTOR(new_pbn,j* SECT_NUM_PER_PAGE);
s_lsn = lpn * SECT_NUM_PER_PAGE;
memset (lsns, 0xFF, sizeof (lsns));
for (h = 0; h< SECT_NUM_PER_PAGE; h++)
{
lsns[h] = s_lsn + h;
}
nand_page_write(s_psn, lsns, 1, 1);
}
else{
}
}
// erase the data block 错误判断
if(old_pbn == PMT[0].pbn){
printf("1. something sucks");
ASSERT(0);
}
nand_erase(old_pbn);
}
}
// erase the log block
nand_erase(PMT[pmt_index].pbn);
free_RW_blk_num++;
}
关于从全合并操作其实可以找到关联的对应的数据块个数,fast.c源码的关键部分就这么些了