1 Star 1 Fork 0

ZhouHeyu / ADFTL

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

CFTL

运行代码需要注意的地方

编译环境

代码是直接运行在Ubuntu10.04的机子上,gcc-2.95,在实验室的那台老机子上,我已经打包压缩好了,注意使用前备份

代码替换注意项

注意本代码不能直接独立编译运行!!!!只是Flashsim修改后的代码 Flashsim的代码必须编译运行在disksim3.0的仿真器上,因此必须先安装成功disksim+flashsim的仿真器后,将主目录下的src换成该CFTL的文件(注意改名为src),重新在主目录下make,编译成功后,进入test.release目录下进行配置操作运行

代码修改运行注意事项

采用git,在自己的电脑进行修改,之后push在老电脑上push同步直接运行编译查看错误修改,建议多了解ssh使用和git使用操作

算法的基本原理说明

CFTL的FTL策略

在SLC采用循环队列策略 在MLC采用的是(师兄对比仿真采用了SDFTL,而原文采用BAST的混合地址映射的方式) 所有的代码SLC和MLC的读写操作都被师兄封装在了dftl.c中 SDFTL的代码逻辑在ssd_interface.c的callFsim中实现

CFTL的数据分配机制

CFTL的数据分配机制根据负载的请求大小来进行热数据识别,并采用2均值聚类算法来自适应调整阈值大小,具体实现操作如下(十分简单): 令C[]为一个一位数组,c[i]表示所有请求中2的i次幂个sector大小的请求的数量,其中接受到的请求最大为1024sector,因此算法中只需要11个计数器即可统计出2的0次幂到2的10次幂返回内写请求的大小的分布情况,注意是针对写请求的,注意对于大小不是2的幂次方的写请求,其大小按照最接近的2的幂次方的值来处理.下面就是2均值聚类的操作,随机选择两个值作为聚类的中心点,bababala,就是那老套路 CFTL聚类后会有一个大的j和小的i的类中心.小的i就是判断阈值,小于i的请求都认为是热的,直接写入到SLC区域,大于i的直接写入到MLC区域.

CFTL的数据迁移机制

CFTL将SLC区域当做循环队列使用,闪存块会按照物理块地址次序一次排列,第一块和最后一个块首位相互链接,如图所示: CFTL数据迁移机制算法示意图

head指向最新的空闲块,tail指向垃圾回收的目标块.初始状态,head和tail指针指向同一个位置,新来的数据写入到head指针指向的块,每当一个空闲块写满后,head指针前移.当SLC的空余块少于一定量的时候,启动垃圾回收机制,回收tail指针指向的块,然后将tail指针前移.任何一个指针超过最后一个物理块的时候,都将该指针指向第一个物理块.通过这种方式,在tail指针指向的物理块的有效数据更新频率更低,因此将tail指针指向的物理块被擦除前,直接将该块中的有效数据迁移到MLC区域中去.

FTL算法相关算法说明

采用CFTL层的仿真代码实现

映射缓存DRAM被分为H-CMT,S-CMT,C-CMT,GTD4个部分.数据块区域用于实际的数据存储,转换块区域用于页级映射表的存储.H-CMT用于缓存访问频率较高的请求的映射项;C-CMT用于缓存访问频率低下的请求的映射项,以及当H-CMT满时从H-CMT剔除的、发生更新的映射项;S-CMT用于缓存高空间本地性请求(连续请求)的映射项.GTD的作用与DFTL的类似,用来记录转换块区域中每个页的地址映射项.其设计结构如图所示: CPFTL算法结构示意图

CPFTL关键结构

HCMT

H-CMT主要是用来缓存访问频繁的请求的映射项.当有请求到来,且请求的映射项在H-CMT中时,就可立即得到服务.当请求不在H-CMT,则到S-CMT 和C-CMT中查询.再者,H-CMT使用LRU策略对每个映射项进行管理.当H-CMT满时,选择最近最少访问的请求的映射项进行剔除.此时,若该映射项已更新,则将其剔除到C-CMT中,反之,则直接从缓存中剔除出去.

C-CMT

C-CMT主要用来缓存访问频率低的请求的映射项.一方面**,大小小于或者等于2KB的请求不在缓存中命中(第一次命中)时,都被认为是访问频率低的请求**,直接加载到C-CMT中;另一方面,C-CMT还存储从H-CMT剔除的、发生更新的映射项.此外,若C-CMT中的映射项被二次访问,同样认为该请求为频繁访问请求,需要将此映射项加载到H-CMT中.再者,C-CMT采用聚簇的思想,即将属于同一转换页中的映射项进行聚簇,并使用LRU策略对所有的簇进行管理,如图所示: C-CMT的聚簇剔除策略

当C-CMT满后,按簇为单位进行剔除.选择剔除簇时,考虑到:一方面,若仅根据簇的映射项个数选择剔除簇,即选择映射项最多的簇进行剔除,则可能会使访问频率低下的簇对应的映射项长时间滞留在C-CMT中,浪费了宝贵的C-CMT缓存空间;另一方面,若仅根据LRU原则选择剔除簇,即选择最近最少访问的簇进行剔除,则可能造成每次剔除回收的缓存空间有限,从而造成经常的地址转换页访问,即增大地址转换页的读写开销. 因此,CPFTL在选择剔除簇时,综合考虑簇的映射项个数和簇在LRU队列中的位置.具体来说,当C-CMT满时,先判断具有最多映射项的簇包含的映射项个数是否大于某个阈值,如果是,选择该簇进行剔除;否则,根据LRU原则选择最近最少访问簇进行剔除

S-CMT

S-CMT主要是用来缓存高空间本地性请求,也即连续请求的映射项.当一个新的请求到来时,如果请求大小大于2KB,CPFTL便认为该请求具有连续性,进而1次加载1组连续的映射项到S-CMT中.当请求在S-CMT再次命中后,则认为该请求为频繁访问请求,并把命中的映射项加载到H-CMT中.再者,S-CMT采用先进先出(first in first out,FIFO)的管理策略,即每次选择停留时间最长的1组映射项进行剔除或更新

CPFTL层的伪代码

主函数逻辑伪代码:

算法1.CPFTL.
 Input: request R with logical page number Rlpn, request size Rsize, request type Rtype;
 LPN = Rlpn, Size = Rsize;
 while Size ≠0 do
  if LPN hits H-CMT or S-CMT or C-CMT then
     service the request; 
       if LPN hits in S-CMT or C-CMT again then /*load the hit entry into H-CMT*/
         load the corresponding entry of LPN into H-CMT using 算法 2;
       end
  elseif Size ≤2 KB then /*load the entry into C-CMT*/
     load the corresponding entry of LPN into C-CMT using 算法 3;
     service the request;
  else/*load the entry into S-CMT*/
     load a group of entries into S-CMT using 算法 4;
       if Rtype is read then
         get the PPN by the entry;
       else /*Rtype is write*/
         service the request with new PPN;
       end
  end
  LPN ++;
  Size --;
 end

加载频繁的访问项到H-CMT中

算法2
 Input: LPN;
 if H-CMT is full then
   select the victim entry according to LRU policy;
   if the victim entry is updated then /*load this entry into C-CMT*/
     update the entries of C-CMT using 算法 3;
   else
     directly discard the entry form H-CMT;
   end
 end
 load the corresponding entry of LPN into H-CMT

将H-CMT中淘汰的数据映射项到C-CMT中

算法3:C-CMT update.
 Input: LPN;
 if C-CMT is full then
   if entries’number of cluster which owns the maximum entries > Threshold then
     evict this cluster from C-CMT;
   else
     evict the least recently used cluster from C-CMT;
   end
 end
 load the corresponding entry of LPN into C-CMT

将判断为连续请求的数据映射项预取到S-CMT中

算法4:S-CMT update.
 Input: LPN;
  if S-CMT is full then
   evict a group of entries from the S-CMT according to FIFO policy;
  end
 load the corresponding entry of LPN and the following some entries into S-CMT

关于CFTL的代码实现的细节

阈值th的更新

CFTL的th是根据一定周期内的请求的大小进行2聚类,以最小的聚类点为阈值,小于这个th的值数据写入到SLC中区,大于写入到MLC中去。 代码逻辑的实现位于源文件disksim_iotrace.c中具体的函数static ioreq_event * iotrace_ascii_get_ioreq_event_1 (FILE *tracefile, ioreq_event *new): 这个函数的全部定义和关键注释如下

static ioreq_event * iotrace_ascii_get_ioreq_event_1 (FILE *tracefile, ioreq_event *new)
{
   char line[201];
   int sbcount,mbcount,threhold,diff,Es,Em,sblkno,ssblkno;
   _u32 RWs,RWm;
   int cnt,i,j,ppn;
   sect_t s_psn,s_psn1,s_lsn;
   blk_t pbn,pin;
   if (fgets(line, 200, tracefile) == NULL) {
      addtoextraq((event *) new);
      return(NULL);
   }
   if (sscanf(line, "%lf %d %d %d %x\n", &new->time, &new->devno, &new->blkno, &new->bcount, &new->flags) != 5) {
      fprintf(stderr, "Wrong number of arguments for I/O trace event type\n");
      fprintf(stderr, "line: %s", line);
      ddbg_assert(0);
   }

   //flashsim
   //SLL_stat_erase_num和MLC_stat_erase_num是全局统计变量,用以统计计算SLC和MLC的磨损速度
   RWs=SLC_stat_erase_num/40960;
   RWm=MLC_stat_erase_num/4096;
  // RWs=SLC_stat_erase_num/1000;
  // RWm=MLC_stat_erase_num/100;
   diff=abs(RWs-RWm);
   Es=SLC_stat_erase_num%40960;
   Em=MLC_stat_erase_num%4096;
   threhold=abs(Es-Em);
   //flag==0就是写请求,需要判断写入的是SLC还是MLC
  if(new->flags==0){ 
     printf("SLC磨损速度:%d\n",SLC_stat_erase_num);
     printf("MLC磨损速度:%d\n",MLC_stat_erase_num);
     w_size[w_count]=new->bcount;                
     w_count++;
    //师兄的仿真确定的CFTL的更新th的周期是1000
     if(w_count==1000){
        w_count=0;
        //但是不是严格意义上的聚类,而是将所有的请求大小进行累加除1000+3??
        for(i=0;i<1000;i++){
          sum+=w_size[i];         
        }
        th=sum/1000+3;
        sum=0;
     } 
     printf("th的值:%d\n ",th);
     //做根据2k数据页的扇区和页对齐
     sblkno=new->blkno;
     sbcount=((new->blkno+ new->bcount-1)/4 - (new->blkno)/4 + 1) * 4;
     sblkno /= 4;
     sblkno *= 4;
     cnt= (sblkno+ sbcount-1)/4 - (sblkno)/4 + 1;
     //判断请求大小和当前阈值决定写入SLC(flash_op_flag=0)
     if(new->bcount<=th){
     	//比阈值小,决定写到SLC中去
        if(RWs<=RWm){
        //当前的SLC磨损速度比MLC低
        //存入SLC的地址都要减去这个基数?
           if(new->blkno>=1048544){
           //1048544转化为地址差不多512MB,(实验SLC最大容量)
              new->blkno=new->blkno-1048544;
           }
           new->flash_op_flag=0;
           new->bcount=((new->blkno+ new->bcount-1)/4 - (new->blkno)/4 + 1) * 4;
           new->blkno /= 4;
           new->blkno *= 4;
        }else{
        //当前SLC的磨损速度高于或等于当前的MLC的速度
           ssblkno=new->blkno;
           if(ssblkno>=1048544){
              ssblkno=ssblkno-1048544;
           }
           //估计是取余数hash了,因为地址差不多1G,而SLC最大的地址为512MB
           if(SLC_opagemap[ssblkno/4].free==0) {
           //要写入的数据页之前的数据页存在SLC中,还是要写入到SLC中更新
              if(new->blkno>=1048544){
                 new->blkno=new->blkno-1048544;
              }
              new->flash_op_flag=0;
              new->bcount=((new->blkno+ new->bcount-1)/4 - (new->blkno)/4 + 1) * 4;
              new->blkno /= 4;
              new->blkno *= 4;
           }else{
           //否则直接写入到MLC中,注意到这里的地址大小没有减去1048544,但页对齐大小是4K的
              new->flash_op_flag=1;
              new->bcount = ((new->blkno+ new->bcount-1)/8 - (new->blkno)/8 + 1) * 8;
              new->blkno /= 8;
              new->blkno *= 8;
           }
        }
     }else{
     	//大请求直接写入到MLC中
        new->flash_op_flag=1;
        new->bcount = ((new->blkno+ new->bcount-1)/8 - (new->blkno)/8 + 1) * 8;
        new->blkno /= 8;
        new->blkno *= 8;
     }
  }else{
  //如果new->flag!=0 读请求,直接交给MLC?
     new->flash_op_flag=1;
     new->bcount = ((new->blkno+ new->bcount-1)/8 - (new->blkno)/8 + 1) * 8;
     new->blkno /= 8;
     new->blkno *= 8;
  }
  //和req队列操作的代码
  if (new->flags & ASYNCHRONOUS) {
     new->flags |= (new->flags & READ) ? TIME_LIMITED : 0;
  } else if (new->flags & SYNCHRONOUS) {
     new->flags |= TIME_CRITICAL;
  }

   new->buf = 0;
   new->opid = 0;
   new->busno = 0;
   new->cause = 0;
   return(new);
}

这里确定函数的调用关系,通过GDB调试确定的,通过bt看到函数的依次调用关系

#0  iotrace_ascii_get_ioreq_event_1 (tracefile=0x955f208, new=0x99bd688)
at disksim_iotrace.c:614
#1  0x08056cb3 in iotrace_get_ioreq_event (tracefile=0x955f208, traceformat=1, 
    temp=0x99bd688) at disksim_iotrace.c:726
#2  0x08059839 in io_get_next_external_event (iotracefile=0x955f208)
    at disksim_iosim.c:678
#3  0x0804bfb5 in getnextevent () at disksim.c:614
#4  0x0804af9c in disksim_simulate_event (num=0) at disksim.c:731
#5  0x0804ba4c in disksim_run_simulation () at disksim.c:1008
#6  0x08049553 in main (argc=6, argv=0xbffff684) at disksim_main.c:192
 

函数 iotrace_get_ioreq_event执行的代码调用片段,存在另一个对应的函数 iotrace_ascii_get_ioreq_event_0

#1  0x08056cb3 in iotrace_get_ioreq_event (tracefile=0x955f208, traceformat=1, 
    temp=0x99bd688) at disksim_iotrace.c:726
726	        temp = iotrace_ascii_get_ioreq_event_1(tracefile, temp);
(gdb) l
721	      
722	   case ASCII:
723	      if(IO_trace==0)
724	         temp = iotrace_ascii_get_ioreq_event_0(tracefile, temp);
725	      else
726	        temp = iotrace_ascii_get_ioreq_event_1(tracefile, temp);
727	      break;
728	      
729	   case RAW:
730	      temp = iotrace_raw_get_ioreq_event(tracefile, temp);
.......

函数 iotrace_ascii_get_ioreq_event_0 iotrace_ascii_get_ioreq_event_1处理差不多,但是该函数是直接判断请求大小小于5,就直接写入SLC,反之MLC

static ioreq_event * iotrace_ascii_get_ioreq_event_0 (FILE *tracefile, ioreq_event *new)
{
   char line[201];
   int sbcount,mbcount;
   if (fgets(line, 200, tracefile) == NULL) {
      addtoextraq((event *) new);
      return(NULL);
   }
   if (sscanf(line, "%lf %d %d %d %x\n", &new->time, &new->devno, &new->blkno, &new->bcount, &new->flags) != 5) {
      fprintf(stderr, "Wrong number of arguments for I/O trace event type\n");
      fprintf(stderr, "line: %s", line);
      ddbg_assert(0);
   }

   //flashsim
   sbcount = ((new->blkno+ new->bcount-1)/4 - (new->blkno)/4 + 1) * 4;
   mbcount = ((new->blkno+ new->bcount-1)/8 - (new->blkno)/8 + 1) * 8;
//   小于固定阈值5就直接网SLC写??
   if(new->bcount<=5){
       // 4
       if(new->blkno>=1048544){
            new->blkno=new->blkno-1048544; 
       }
       new->bcount=((new->blkno+ new->bcount-1)/4 - (new->blkno)/4 + 1) * 4;
       new->blkno /= 4;
       new->blkno *= 4;
       new->flash_op_flag=0; 
   } else{ 
       new->bcount=((new->blkno+ new->bcount-1)/8 - (new->blkno)/8 + 1) * 8;
       new->blkno /= 8;
       new->blkno *= 8;
       new->flash_op_flag=1; 
   }
   
   
   if (new->flags & ASYNCHRONOUS) {
      new->flags |= (new->flags & READ) ? TIME_LIMITED : 0;
   } else if (new->flags & SYNCHRONOUS) {
      new->flags |= TIME_CRITICAL;
   }

   new->buf = 0;
   new->opid = 0;
   new->busno = 0;
   new->cause = 0;
   return(new);
}

这里涉及变量IO_trace该全局变量在两个地方进行置位一个是在函数disksim_run_simulation ()

void disksim_run_simulation ()
{
  int event_count = 0;
  delay2=0;
  IO_trace=1;
  while (disksim->stop_sim == FALSE) {
    disksim_simulate_event(event_count);
    event_count++;
  }
//  printf("disksim_run_simulation(): simulated %d events\n", event_count);
}

另一个置位是在prime_simulation ()

static void prime_simulation ()
{
   event *curr;
   IO_trace=0;
   if (disksim->warmup_event) {
      addtointq((event *)disksim->warmup_event);
      disksim->warmup_event = NULL;
   }
   if (disksim->checkpoint_interval > 0.0) {
      disksim_register_checkpoint (disksim->checkpoint_interval);
   }
   if (disksim->iotrace) {
      if ((curr = io_get_next_external_event(disksim->iotracefile)) == NULL) {
         disksim_cleanstats();
         return;
      }
      if ((disksim->traceformat != VALIDATE) && (disksim->closedios == 0)) {
         curr->type = NULL_EVENT;
      } else if (disksim->closedios) {
         int iocnt;
         io_using_external_event(curr);
         curr->time = simtime + disksim->closedthinktime;
         for (iocnt=1; iocnt < disksim->closedios; iocnt++) {
            curr->next = io_get_next_external_event(disksim->iotracefile);
            if (curr->next) {
               io_using_external_event(curr->next);
               curr->time = simtime + disksim->closedthinktime;
               addtointq(curr->next);
            }
         }
      }
      addtointq(curr);
   }
}

根据GDB调试,查看函数执行次序,prime_simulation ()在预热之前设置,即预热的时候,采用的是固定的阈值5写入的,之后预热完,执行函数disksim_run_simulation ()则采用CFTL的周期更改阈值的方式。

关于SLC和MLC的比例修改,如何修改,设计修改的变量的地方

首先查看ssd_interface.c的函数initFlash的关键代码片段

  blk_t total_blk_num;
  blk_t total_SLC_blk_num;
  blk_t total_MLC_blk_num;
  blk_t total_util_blk_num;
  blk_t total_extr_blk_num;
  blk_t total_SLC_util_blk_num;
  blk_t total_SLC_extr_blk_num;
  blk_t total_MLC_util_blk_num;
  blk_t total_MLC_extr_blk_num;

  // total number of sectors    
  // 相关的SLC的宏定义在disksim_global.h中
  //   #define total_SLC_util_sect_num  1048576//1048576 --->(2k) 4096个块  512MB
  // #define total_SLC_extra_sect_num  32768//32768 -->(2k)  128个空闲块 16MB
  // 所以在配置文件应该配置为2G,保证MLC有1.5GB左右的剩余地址
  total_util_sect_num  = flash_numblocks;
  total_extra_sect_num = flash_extrblocks;
  total_MLC_util_sect_num  = total_util_sect_num-total_SLC_util_sect_num;
  total_MLC_extra_sect_num = total_extra_sect_num-total_SLC_extra_sect_num;
  total_sect_num = total_util_sect_num + total_extra_sect_num;
  total_SLC_sect_num=total_SLC_util_sect_num+total_SLC_extra_sect_num;
  total_MLC_sect_num=total_MLC_util_sect_num+total_MLC_extra_sect_num; 

  // total number of blocks 
  total_SLC_blk_num  = total_SLC_sect_num / S_SECT_NUM_PER_BLK;     
  total_MLC_blk_num  = total_MLC_sect_num / M_SECT_NUM_PER_BLK;
  total_blk_num =total_SLC_blk_num + total_MLC_blk_num; 

  total_SLC_util_blk_num = total_SLC_util_sect_num / S_SECT_NUM_PER_BLK;    
  total_MLC_util_blk_num = total_MLC_util_sect_num / M_SECT_NUM_PER_BLK;
  total_SLC_extr_blk_num = total_SLC_extra_sect_num / S_SECT_NUM_PER_BLK;
  total_MLC_extr_blk_num = total_MLC_extra_sect_num / M_SECT_NUM_PER_BLK;
  total_util_blk_num = total_SLC_util_blk_num + total_MLC_util_blk_num;
  global_total_blk_num = total_util_blk_num;

  total_extr_blk_num = total_blk_num - total_util_blk_num;        // total extra block number

总的来说SLC的大小通过代码写死,配置文件里面的设定大小减去SLC为MLC的大小 但SLC的宏大小会影响一下函数中的变量(目前已知)

iotrace_ascii_get_ioreq_event_0()
iotrace_ascii_get_ioreq_event_1()
写入SLC的减去的偏移量1048544
dftl.cSLC的写入(因为采用循环队列,头尾块的截止地址)
同时和SLC_opagemap的个数应该紧密相关

因为CFTL的SLC的数据迁移直接将尾部的回收块中的有效数据页直接回写到MLC中,不存在N次策略的问题。所以dftl.c中的SLC to MLC较为简单

MIT License Copyright (c) 2018 ZhouJie Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

全自适应的混合SSD磨损均衡和基于DFTL的改进算法(该代码版本已经废弃) 展开 收起
C
MIT
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
C
1
https://gitee.com/ZhouHeyu/ADFTL.git
git@gitee.com:ZhouHeyu/ADFTL.git
ZhouHeyu
ADFTL
ADFTL
master

搜索帮助