Cosmos OpenSSD--greedy_ftl1.2.0(三)
我们来假设模拟一个小型的模型来分析写和垃圾回收的过程
假设只有一个die,4个block,每个block4个page,每个page8KB
那么PageMap就是Page[0][0]到Page[0][15]
BlockMap就是Block[0][0]到Block[0][3]
GcMap就是GC[0][0]到GC[0][4]
那么最初就会如下图:第零块用来保留元数据,最后一块保留作为垃圾回收合并块
接下来从上层来了数据,黄色部分表示更改的数据,红色部分表示更新的数据,最左侧是流程
在这里,lpn0的内容作为page缓存留在SDRAM里面,只把lpn1的内容存入flash
接下来更改的数据缓存命中了,所以lpn0的内容直接在SDRAM里面修改,此时数据末尾刚好占据了一个页,所以直接存入flash,并把之前的保存页无效,此时GC表开始变化
接下来依旧缓存命中,但是请求最后一页并不是完整的一页,于是先预读出其中的内容,修改之后寻找新页再保存进flash
这一次的缓存并没有命中,所以先把SDRAM里面的内容flush进SDRAM里面,因为请求并不满足一页,所以执行read-modify-write,此时lpn1作为新的page缓存,并不直接存进flash而是保留在SDRAM中
缓存依旧不命中,此时block1已经没有页面可用了,于是找到了下一个空闲block,先flush,然后更新page缓存,将lpn1的内容存入flash
接下来缓存不命中,并且消耗掉了所有的页面,于是lpn3的内容无处存取,引发垃圾回收操作
垃圾回收操作的依据是元数据的更新,因此我们来看元数据更新的代码
1 void UpdateMetaForOverwrite(u32 lpn) 2 { 3 pageMap = (struct pmArray*)(PAGE_MAP_ADDR); 4 blockMap = (struct bmArray*)(BLOCK_MAP_ADDR); 5 gcMap = (struct gcArray*)(GC_MAP_ADDR); 6 7 u32 dieNo = lpn % DIE_NUM; 8 u32 dieLpn = lpn / DIE_NUM; 9 10 if(pageMap->pmEntry[dieNo][dieLpn].ppn != 0xffffffff) 11 { 12 // GC victim block list management 13 u32 diePbn = pageMap->pmEntry[dieNo][dieLpn].ppn / PAGE_NUM_PER_BLOCK; 14 15 // unlink 16 if((blockMap->bmEntry[dieNo][diePbn].nextBlock != 0xffffffff) && (blockMap->bmEntry[dieNo][diePbn].prevBlock != 0xffffffff)) 17 { 18 blockMap->bmEntry[dieNo][blockMap->bmEntry[dieNo][diePbn].prevBlock].nextBlock = blockMap->bmEntry[dieNo][diePbn].nextBlock; 19 blockMap->bmEntry[dieNo][blockMap->bmEntry[dieNo][diePbn].nextBlock].prevBlock = blockMap->bmEntry[dieNo][diePbn].prevBlock; 20 } 21 else if((blockMap->bmEntry[dieNo][diePbn].nextBlock == 0xffffffff) && (blockMap->bmEntry[dieNo][diePbn].prevBlock != 0xffffffff)) 22 { 23 blockMap->bmEntry[dieNo][blockMap->bmEntry[dieNo][diePbn].prevBlock].nextBlock = 0xffffffff; 24 gcMap->gcEntry[dieNo][blockMap->bmEntry[dieNo][diePbn].invalidPageCnt].tail = blockMap->bmEntry[dieNo][diePbn].prevBlock; 25 } 26 else if((blockMap->bmEntry[dieNo][diePbn].nextBlock != 0xffffffff) && (blockMap->bmEntry[dieNo][diePbn].prevBlock == 0xffffffff)) 27 { 28 blockMap->bmEntry[dieNo][blockMap->bmEntry[dieNo][diePbn].nextBlock].prevBlock = 0xffffffff; 29 gcMap->gcEntry[dieNo][blockMap->bmEntry[dieNo][diePbn].invalidPageCnt].head = blockMap->bmEntry[dieNo][diePbn].nextBlock; 30 } 31 else 32 { 33 gcMap->gcEntry[dieNo][blockMap->bmEntry[dieNo][diePbn].invalidPageCnt].head = 0xffffffff; 34 gcMap->gcEntry[dieNo][blockMap->bmEntry[dieNo][diePbn].invalidPageCnt].tail = 0xffffffff; 35 } 36 37 // xil_printf("[unlink] dieNo = %d, invalidPageCnt= %d, diePbn= %d, blockMap.prevBlock= %d, blockMap.nextBlock= %d, gcMap.head= %d, gcMap.tail= %d\r\n", dieNo, blockMap->bmEntry[dieNo][diePbn].invalidPageCnt, diePbn, blockMap->bmEntry[dieNo][diePbn].prevBlock, blockMap->bmEntry[dieNo][diePbn].nextBlock, gcMap->gcEntry[dieNo][blockMap->bmEntry[dieNo][diePbn].invalidPageCnt].head, gcMap->gcEntry[dieNo][blockMap->bmEntry[dieNo][diePbn].invalidPageCnt].tail); 38 39 // invalidation update 40 pageMap->pmEntry[dieNo][pageMap->pmEntry[dieNo][dieLpn].ppn].valid = 0; 41 blockMap->bmEntry[dieNo][diePbn].invalidPageCnt++; 42 43 // insertion 44 if(gcMap->gcEntry[dieNo][blockMap->bmEntry[dieNo][diePbn].invalidPageCnt].tail != 0xffffffff) 45 { 46 blockMap->bmEntry[dieNo][diePbn].prevBlock = gcMap->gcEntry[dieNo][blockMap->bmEntry[dieNo][diePbn].invalidPageCnt].tail; 47 blockMap->bmEntry[dieNo][diePbn].nextBlock = 0xffffffff; 48 blockMap->bmEntry[dieNo][gcMap->gcEntry[dieNo][blockMap->bmEntry[dieNo][diePbn].invalidPageCnt].tail].nextBlock = diePbn; 49 gcMap->gcEntry[dieNo][blockMap->bmEntry[dieNo][diePbn].invalidPageCnt].tail = diePbn; 50 } 51 else 52 { 53 blockMap->bmEntry[dieNo][diePbn].prevBlock = 0xffffffff; 54 blockMap->bmEntry[dieNo][diePbn].nextBlock = 0xffffffff; 55 gcMap->gcEntry[dieNo][blockMap->bmEntry[dieNo][diePbn].invalidPageCnt].head = diePbn; 56 gcMap->gcEntry[dieNo][blockMap->bmEntry[dieNo][diePbn].invalidPageCnt].tail = diePbn; 57 } 58 } 59 }
其实从上面的写过程分析,我们可以大概了解到GC表其实就是把有相同无效页的block串联在一起
我们假设从block1到block7,其中无效页最终分别为100,120,110,100,110,120,100
当block1进行完之后,GC[0][100].head=1 GC[0][100].tail=1
当block2的无效页也到达100的时候,分析代码可知,GC[0][100].head=1 GC[0][100].tail=2,而且block1和block2会链接起来,但是block2的无效页会继续增加,于是当他的无效页增加的时候,block1和block2又会断开,指向无效的0xffffffff,而block2执行完毕之后GC[0][120].head=2 GC[0][120].tail=2.
只有当block4和block7的无效页最终停在100的时候,block1↔block4↔block7
既然已经了解GC表表示的什么意思,那么再看垃圾回收操作的代码就简单多了
1 u32 GarbageCollection(u32 dieNo) 2 { 3 xil_printf("GC occurs!\r\n"); 4 5 pageMap = (struct pmArray*)(PAGE_MAP_ADDR); 6 blockMap = (struct bmArray*)(BLOCK_MAP_ADDR); 7 dieBlock = (struct dieArray*)(DIE_MAP_ADDR); 8 gcMap = (struct gcArray*)(GC_MAP_ADDR); 9 10 int i; 11 for(i=PAGE_NUM_PER_BLOCK ; i>0 ; i--) //从无效页大的开始回收 12 { 13 if(gcMap->gcEntry[dieNo][i].head != 0xffffffff) //拥有这么多无效页的块存在的话,取出一块进行回收 14 { 15 u32 victimBlock = gcMap->gcEntry[dieNo][i].head; // GC victim block 16 17 // link setting 18 if(blockMap->bmEntry[dieNo][victimBlock].nextBlock != 0xffffffff) //更新GC链表 19 { 20 gcMap->gcEntry[dieNo][i].head = blockMap->bmEntry[dieNo][victimBlock].nextBlock; 21 blockMap->bmEntry[dieNo][blockMap->bmEntry[dieNo][victimBlock].nextBlock].prevBlock = 0xffffffff; 22 } 23 else 24 { 25 gcMap->gcEntry[dieNo][i].head = 0xffffffff; 26 gcMap->gcEntry[dieNo][i].tail = 0xffffffff; 27 } 28 29 // copy valid pages from the victim block to the free block 30 if(i != PAGE_NUM_PER_BLOCK) //如果整个块都是无效页就直接擦除就行了 31 { 32 int j; 33 for(j=0 ; j<PAGE_NUM_PER_BLOCK ; j++) // 34 { 35 if(pageMap->pmEntry[dieNo][(victimBlock * PAGE_NUM_PER_BLOCK) + j].valid) 36 { 37 // page copy process 38 u32 validPage = victimBlock*PAGE_NUM_PER_BLOCK + j; 39 u32 freeBlock = dieBlock->dieEntry[dieNo].freeBlock; //最开始有预留一个块 40 u32 freePage = freeBlock*PAGE_NUM_PER_BLOCK + blockMap->bmEntry[dieNo][freeBlock].currentPage; 41 42 WaitWayFree(dieNo % CHANNEL_NUM, dieNo / CHANNEL_NUM); 43 SsdRead(dieNo % CHANNEL_NUM, dieNo / CHANNEL_NUM, validPage, GC_BUFFER_ADDR); //将一个块里面的有效页读取到BUFFER里面 44 WaitWayFree(dieNo % CHANNEL_NUM, dieNo / CHANNEL_NUM); 45 SsdProgram(dieNo % CHANNEL_NUM, dieNo / CHANNEL_NUM, freePage, GC_BUFFER_ADDR); //有效数据写入 46 47 // pageMap, blockMap update 48 u32 lpn = pageMap->pmEntry[dieNo][validPage].lpn; 49 50 pageMap->pmEntry[dieNo][lpn].ppn = freePage; //更新页映射信息 51 pageMap->pmEntry[dieNo][freePage].lpn = lpn; 52 blockMap->bmEntry[dieNo][freeBlock].currentPage++; 53 } 54 } 55 } 56 57 // erased victim block becomes the free block for GC migration 58 EraseBlock(dieNo, victimBlock); 59 blockMap->bmEntry[dieNo][victimBlock].free = 0; 60 61 u32 currentBlock = dieBlock->dieEntry[dieNo].freeBlock; 62 dieBlock->dieEntry[dieNo].freeBlock = victimBlock; //将刚擦出的块作为新的freeblock 63 64 return currentBlock; // atomic GC completion 65 } 66 } 67 68 // no free space anymore 69 assert(!"[WARNING] There are no free blocks. Abort terminate this ssd. [WARNING]"); 70 return 1; 71 }