Cosmos OpenSSD--greedy_ftl1.2.0(二)
FTL的整个流程如下:
下面先来看写的流程:
写的代码如下:
1 if((hostCmd.reqInfo.Cmd == IDE_COMMAND_WRITE_DMA) || (hostCmd.reqInfo.Cmd == IDE_COMMAND_WRITE)) 2 { 3 // xil_printf("write(%d, %d)\r\n", hostCmd.reqInfo.CurSect, hostCmd.reqInfo.ReqSect); 4 5 PrePmRead(&hostCmd, RAM_DISK_BASE_ADDR); 6 7 deviceAddr = RAM_DISK_BASE_ADDR + (hostCmd.reqInfo.CurSect % SECTOR_NUM_PER_PAGE)*SECTOR_SIZE; 8 reqSize = hostCmd.reqInfo.ReqSect * SECTOR_SIZE; 9 scatterLength = hostCmd.reqInfo.HostScatterNum; 10 11 DmaHostToDevice(&hostCmd, deviceAddr, reqSize, scatterLength); 12 13 PmWrite(&hostCmd, RAM_DISK_BASE_ADDR); 14 15 CompleteCmd(&hostCmd); 16 }
首先来看PrePmRead,其中最开始会涉及一个FlushPageBuf函数,FlushPageBuf里面有个FindFreePage函数,所以我们先分析FindFreePage函数的功能
lpn = hostCmd->reqInfo.CurSect / SECTOR_NUM_PER_PAGE;
u32 dieNo = lpn % DIE_NUM;
这里传入一个dieNo参数
1 int FindFreePage(u32 dieNo) 2 { 3 blockMap = (struct bmArray*)(BLOCK_MAP_ADDR); 4 dieBlock = (struct dieArray*)(DIE_MAP_ADDR); 5 6 if(blockMap->bmEntry[dieNo][dieBlock->dieEntry[dieNo].currentBlock].currentPage == PAGE_NUM_PER_BLOCK-1) //当前块已经写完最后一页,则用下一个block 7 { 8 dieBlock->dieEntry[dieNo].currentBlock++; 9 10 int i; 11 for(i=dieBlock->dieEntry[dieNo].currentBlock ; i<(dieBlock->dieEntry[dieNo].currentBlock + BLOCK_NUM_PER_DIE) ; i++) /*遍历整个die的所有block,到结尾之后又从开始找,直到找到一个可用的block*/ 12 { 13 if((blockMap->bmEntry[dieNo][i % BLOCK_NUM_PER_DIE].free) && (!blockMap->bmEntry[dieNo][i % BLOCK_NUM_PER_DIE].bad)) //块free且不是坏块就可用 14 { 15 blockMap->bmEntry[dieNo][i % BLOCK_NUM_PER_DIE].free = 0; 16 dieBlock->dieEntry[dieNo].currentBlock = i % BLOCK_NUM_PER_DIE; 17 18 // xil_printf("allocated free block: %4d at %d-%d\r\n", dieBlock->dieEntry[dieNo].currentBlock, dieNo % CHANNEL_NUM, dieNo / CHANNEL_NUM); 19 20 return dieBlock->dieEntry[dieNo].currentBlock * PAGE_NUM_PER_BLOCK; //返回页号 21 } 22 } 23 24 dieBlock->dieEntry[dieNo].currentBlock = GarbageCollection(dieNo); //整个die没有可用块之后就进行垃圾回收 25 26 // xil_printf("allocated free block by GC: %4d at %d-%d\r\n", dieBlock->dieEntry[dieNo].currentBlock, dieNo % CHANNEL_NUM, dieNo / CHANNEL_NUM); 27 28 return (dieBlock->dieEntry[dieNo].currentBlock * PAGE_NUM_PER_BLOCK) + blockMap->bmEntry[dieNo][dieBlock->dieEntry[dieNo].currentBlock].currentPage; 29 } 30 else //当前块还有页可用就直接接着上一页继续写 31 { 32 blockMap->bmEntry[dieNo][dieBlock->dieEntry[dieNo].currentBlock].currentPage++; 33 return (dieBlock->dieEntry[dieNo].currentBlock * PAGE_NUM_PER_BLOCK) + blockMap->bmEntry[dieNo][dieBlock->dieEntry[dieNo].currentBlock].currentPage; 34 } 35 }
由此可见,FindFreePage这个函数其实就是找一个可用的页,没有空间了就进行垃圾回收操作
接下来看上一级的函数FlushPageBuf
1 void FlushPageBuf(u32 lpn, u32 bufAddr) 2 { 3 if (lpn == 0xffffffff) //最开始page缓存内是没有东西的,所以无需flush 4 return; 5 6 u32 dieNo = lpn % DIE_NUM; //计算出die number 7 u32 dieLpn = lpn / DIE_NUM; //计算出lpn在die中是第几个lpn,可以理解为die0上是lpn0,lpn16……对应为dieLpn0,dieLpn1 8 u32 ppn = pageMap->pmEntry[dieNo][dieLpn].ppn; 9 10 if (ppn == 0xffffffff) //表示page缓存还没有写入ppn 11 { 12 u32 freePageNo = FindFreePage(dieNo); 13 14 // xil_printf("free page: %6d(%d, %d, %4d)\r\n", freePageNo, dieNo%CHANNEL_NUM, dieNo/CHANNEL_NUM, freePageNo/PAGE_NUM_PER_BLOCK); 15 16 WaitWayFree(dieNo % CHANNEL_NUM, dieNo / CHANNEL_NUM); 17 SsdProgram(dieNo % CHANNEL_NUM, dieNo / CHANNEL_NUM, freePageNo, bufAddr); 18 WaitWayFree(dieNo % CHANNEL_NUM, dieNo / CHANNEL_NUM); 19 20 // pageMap update 21 pageMap->pmEntry[dieNo][dieLpn].ppn = freePageNo; 22 pageMap->pmEntry[dieNo][freePageNo].lpn = dieLpn; 23 } 24 }
继续来看再上一级PrePmRead函数
int PrePmRead(P_HOST_CMD hostCmd, u32 bufferAddr) { u32 lpn; u32 dieNo; u32 dieLpn; pageMap = (struct pmArray*)(PAGE_MAP_ADDR); lpn = hostCmd->reqInfo.CurSect / SECTOR_NUM_PER_PAGE; if (lpn != pageBufLpn) //新的请求和上个请求不是同一个lpn { FlushPageBuf(pageBufLpn, bufferAddr);
上面这一段进行了FlushPageBuf操作
if((((hostCmd->reqInfo.CurSect)%SECTOR_NUM_PER_PAGE) != 0) || ((hostCmd->reqInfo.CurSect / SECTOR_NUM_PER_PAGE) == (((hostCmd->reqInfo.CurSect)+(hostCmd->reqInfo.ReqSect))/SECTOR_NUM_PER_PAGE))) { dieNo = lpn % DIE_NUM; dieLpn = lpn / DIE_NUM; if(pageMap->pmEntry[dieNo][dieLpn].ppn != 0xffffffff) { // xil_printf("PrePmRead pdie, ppn = %d, %d\r\n", dieNo, pageMap->pmEntry[dieNo][dieLpn].ppn); WaitWayFree(dieNo % CHANNEL_NUM, dieNo / CHANNEL_NUM); SsdRead(dieNo % CHANNEL_NUM, dieNo / CHANNEL_NUM, pageMap->pmEntry[dieNo][dieLpn].ppn, bufferAddr); WaitWayFree(dieNo % CHANNEL_NUM, dieNo / CHANNEL_NUM); pageBufLpn = lpn; } } }
疑问:这个判断条件第一个是请求开端不要是每个lpn的开始,第二个是请求的大小在同一页,那么这个条件就是只要满足不是请求跨页且从一个lpn的开始的就进入判断?有什么实际意义呢?
答:这里很关键,涉及一段数据,假如开始的地方没有和page对齐的话,那么在每个page里面请求开始前面的数据就得先读出来,如果是缓存命中的话,就无需操作,因为可以直接修改,那么没命中的话,即使是page对齐了,如果数据没有跨页的话,也还是要读出来,不然请求末尾内page的内容就丢失了。如下图
假如我要改写345678这些数据,因为数据是按页保存的,所以我只修改这些数据的话,我还得绿色部分的读取出来,然后修改后一起保存到一个页里面,所以请求的开始的lpn如果不是页对齐,我就得read-modify-write,同理,即使页对齐了,但是数据不足一页,那么一页后面几项数据也得先读出来。如果对齐且大小刚好等于一页的话,if失败,这个时候一页也是刚好可以直接修改。至于中间页的数据,本来就是一整页的,所以直接把原来的页无效,然后写入新页即可。这里这个判断条件是在页缓存没有命中的情况下,如果命中了,因为此页还没有刷新到nandflash,所以就无需取出而直接在SDRAM里面修改就行了
if(((((hostCmd->reqInfo.CurSect)+(hostCmd->reqInfo.ReqSect))% SECTOR_NUM_PER_PAGE) != 0) && ((hostCmd->reqInfo.CurSect / SECTOR_NUM_PER_PAGE) != (((hostCmd->reqInfo.CurSect)+(hostCmd->reqInfo.ReqSect))/SECTOR_NUM_PER_PAGE))) { lpn = ((hostCmd->reqInfo.CurSect)+(hostCmd->reqInfo.ReqSect))/SECTOR_NUM_PER_PAGE; dieNo = lpn % DIE_NUM; dieLpn = lpn / DIE_NUM; if(pageMap->pmEntry[dieNo][dieLpn].ppn != 0xffffffff) { // xil_printf("PrePmRead pdie, ppn = %d, %d\r\n", dieNo, pageMap->pmEntry[dieNo][dieLpn].ppn); WaitWayFree(dieNo % CHANNEL_NUM, dieNo / CHANNEL_NUM); SsdRead(dieNo % CHANNEL_NUM, dieNo / CHANNEL_NUM, pageMap->pmEntry[dieNo][dieLpn].ppn, bufferAddr + ((((hostCmd->reqInfo.CurSect)% SECTOR_NUM_PER_PAGE) + hostCmd->reqInfo.ReqSect)/SECTOR_NUM_PER_PAGE*PAGE_SIZE)); WaitWayFree(dieNo % CHANNEL_NUM, dieNo / CHANNEL_NUM); } } return 0; }
上面是头,那这一部分就是尾,尾部如果不是请求对齐到page尾的话,那么也会有数据得不到更新,就如同上图lpn1的9到15,要把page内没修改的数据一起读出来更新,这里不仅是要不是结尾,而且是要跨页,这里分几种情况,假如缓存命中,没有跨页的话直接更新缓存就行了,只有跨页了,才需要进行read-modify-write操作;假如缓存没有命中,系统先把之前的缓存flush到nandflash里面,如果此时数据没有跨页的话,那么上面的操作就已经会读取那个页,也就无需下面再多此一举了,具体的写操作可以看下一篇文档。
接下来来看真正的写操作PmWrite
1 int PmWrite(P_HOST_CMD hostCmd, u32 bufferAddr) 2 { 3 u32 tempBuffer = bufferAddr; 4 5 u32 lpn = hostCmd->reqInfo.CurSect / SECTOR_NUM_PER_PAGE; 6 7 int loop = (hostCmd->reqInfo.CurSect % SECTOR_NUM_PER_PAGE) + hostCmd->reqInfo.ReqSect; 8 9 u32 dieNo; 10 u32 dieLpn; 11 u32 freePageNo; 12 13 pageMap = (struct pmArray*)(PAGE_MAP_ADDR); 14 15 // page buffer utilization 16 if (lpn != pageBufLpn) 17 pageBufLpn = lpn; 18 19 UpdateMetaForOverwrite(lpn); 20 21 // pageMap update 22 dieNo = lpn % DIE_NUM; 23 dieLpn = lpn / DIE_NUM; 24 pageMap->pmEntry[dieNo][dieLpn].ppn = 0xffffffff; //写入一页不立即更新 25 26 lpn++; 27 tempBuffer += PAGE_SIZE; 28 loop -= SECTOR_NUM_PER_PAGE; 29 30 while(loop > 0) //接下来还有页请求的话,寻找新页,写入,更新页表 31 { 32 dieNo = lpn % DIE_NUM; 33 dieLpn = lpn / DIE_NUM; 34 freePageNo = FindFreePage(dieNo); 35 36 // xil_printf("free page: %6d(%d, %d, %4d)\r\n", freePageNo, dieNo%CHANNEL_NUM, dieNo/CHANNEL_NUM, freePageNo/PAGE_NUM_PER_BLOCK); 37 38 WaitWayFree(dieNo % CHANNEL_NUM, dieNo / CHANNEL_NUM); 39 SsdProgram(dieNo % CHANNEL_NUM, dieNo / CHANNEL_NUM, freePageNo, tempBuffer); 40 41 UpdateMetaForOverwrite(lpn); 42 43 // pageMap update 44 pageMap->pmEntry[dieNo][dieLpn].ppn = freePageNo; 45 pageMap->pmEntry[dieNo][freePageNo].lpn = dieLpn; 46 47 lpn++; 48 tempBuffer += PAGE_SIZE; 49 loop -= SECTOR_NUM_PER_PAGE; 50 } 51 52 int i; 53 for(i=0 ; i<DIE_NUM ; ++i) 54 WaitWayFree(i%CHANNEL_NUM, i/CHANNEL_NUM); 55 56 return 0; 57 }
接下来看读
1 int PmRead(P_HOST_CMD hostCmd, u32 bufferAddr) 2 { 3 u32 tempBuffer = bufferAddr; 4 5 u32 lpn = hostCmd->reqInfo.CurSect / SECTOR_NUM_PER_PAGE; 6 int loop = (hostCmd->reqInfo.CurSect % SECTOR_NUM_PER_PAGE) + hostCmd->reqInfo.ReqSect; 7 8 u32 dieNo; 9 u32 dieLpn; 10 11 pageMap = (struct pmArray*)(PAGE_MAP_ADDR); 12 13 if (lpn == pageBufLpn) //缓存命中,就无需读取第一页,直接就在内存里面 14 { 15 lpn++; 16 tempBuffer += PAGE_SIZE; 17 loop -= SECTOR_NUM_PER_PAGE; 18 } 19 else 20 { 21 dieNo = lpn % DIE_NUM; 22 dieLpn = lpn / DIE_NUM; 23 24 if(pageMap->pmEntry[dieNo][dieLpn].ppn != 0xffffffff) //防止第一次读空页 25 { 26 FlushPageBuf(pageBufLpn, bufferAddr); 27 pageBufLpn = lpn; 28 } 29 } 30 31 while(loop > 0) //把接下来的页一次读取出来 32 { 33 dieNo = lpn % DIE_NUM; 34 dieLpn = lpn / DIE_NUM; 35 36 // xil_printf("requested read lpn = %d\r\n", lpn); 37 // xil_printf("read pdie, ppn = %d, %d\r\n", dieNo, pageMap->pmEntry[dieNo][dieLpn].ppn); 38 39 if(pageMap->pmEntry[dieNo][dieLpn].ppn != 0xffffffff) 40 { 41 // xil_printf("read at (%d, %2d, %4x)\r\n", dieNo%CHANNEL_NUM, dieNo/CHANNEL_NUM, pageMap->pmEntry[dieNo][dieLpn].ppn); 42 43 WaitWayFree(dieNo % CHANNEL_NUM, dieNo / CHANNEL_NUM); 44 SsdRead(dieNo % CHANNEL_NUM, dieNo / CHANNEL_NUM, pageMap->pmEntry[dieNo][dieLpn].ppn, tempBuffer); 45 } 46 47 lpn++; 48 tempBuffer += PAGE_SIZE; 49 loop -= SECTOR_NUM_PER_PAGE; 50 } 51 52 int i; 53 for(i=0 ; i<DIE_NUM ; ++i) 54 WaitWayFree(i%CHANNEL_NUM, i/CHANNEL_NUM); 55 56 return 0; 57 }