SSDsim源码分析之pre_process_page

来源:互联网 发布:crm软件系统 编辑:程序博客网 时间:2024/06/18 13:27

pre_process_page()


从本篇博文开始,我们将会对SSDsim中最核心的部分进行详细的分析和注解,SSDsim仿真器最核心的部分在于三大函数:

pre_preocess_page()页读请求预处理函数
simmulate()核心模拟函数
statistic_output()统计输出信息函数

其中最为重要和庞大的函数当属simmulate()核心模拟函数,这个函数主要是负责整个SSD功能的模拟,我们会在后面的篇幅中将其展开详细描述并且加以解析;而本篇博文先解析pre_preocess_page()函数的主要功能和详细的作用,这其中会涉及到很多SSDsim的工作原理以及架构方面的知识,可以结合程序源码及解析进行分析。


pre_process_page()主要作用与功能

Pre_process_page()读请求预处理函数,主要功能是当读请求所读的页page内没有数据时需要该函数往page内先写入data以完成读操作。同时也是因为要将tracefile中所有的读请求IO全部先进行优先处理.

函数首先会定义一些相关的参数,其中包括了一个长度为200char的buf缓冲区,并且调用fopen()以只读方式打开tracefile文件,若打开失败(文件不存在或者文件名有误等情况)则直接返回出错。

若读取tracefile成功,程序会先置full_page即子页屏蔽码为1(初始屏蔽码默认每一个page下的所有subpage的状态都是为1),计算:

largest_lsn最大的逻辑扇区号 =( SSD的chip数量 × 每个chip下的die数量 × 每个die下的plane数量 × 每个plane下的block数量 × 每个block下的page数量 × 每个page下的subpage数量) × 预留空间OP比率。
注:SSD都会有一个OP预留空间的概念,这个OP预留空间可以很大程度上提高SSD的性能。

随后程序会调用fget()逐行打开tracefile中的所有IOtrace信息到buf缓冲区中,每次最多读取一行200个char;

在打开读取trace的过程中,程序利用了sscanf()函数按照:
time(long long型) device(int型) lsn(int型) size(int型) ope(int型)。

这样的固定trace格式逐行读取IOtrace数据,读完后便统计fl也就是IO数量计数,并且每次读完一个IOtrace都需要调用trace_assert()函数进行IO的正确性判断。
随后初始化add_size该IO已完成的size计数为0,判断ope操作数

若ope为0则说明是写请求,但是由于这个pre_process_page只是服务于读请求,所以程序流会忽略写请求直接继续调用fget()读取下一个IOtrace进行读请求的操作。

而当ope为1时说明IO为读请求,程序会判断已完成的IO长度add_size是否小于IO长度size,若大于或等于size说明读IO已经处理完毕继续下一IO的读取;否则说明该IO读尚未处理完毕,要继续下面的步骤:

首先程序会调整lsn(防止lsn比最大的lsn还大),然后计算sub_size即lsn所在的page中实际IO需要操作的长度:

这部分长度 = page子页数量 - lsn在page中的起始子页位置

随后判断已经处理的IO长度add_size当前page中的实际操作长度sub_size之和是否超过该IOtrace的长度size

若已经超过说明sub_size实际上的长度需要调整(实际上不可能超过IOtrace所规定的size,否则就是错误操作,而这里的sub_size主要是针对首次的lsn和最后一次的lsn,因为其余中间部分的lsn基本上都会是从page页的起始位置开始,操作的长度都会是整个page大小)

所以通常是最后一页的lsn可能需要进行sub_size的调整,此时

sub_size = IO长度size - 已经处理完毕的长度add_size

同时更新已经处理完毕的长度add_size令其加上调整后的sub_size;

若判断中未超过时说明此时处理的长度尚未到达最后一页,那么可以直接跳过上述调整步骤直接判断sub_size的合法性(即是否超过page大小)和已经处理完毕的长度add_size是否合理(即是否超过了该IOtrace的长度size)

如果非法的话则打印sub_size的信息;同时程序会继续统计当前lsn所在的lpn,并判断该lpn在内存dram中的映射表项map_entry[lpn]的相关子页映射是否有效:

map_entry[lpn].state不为0则说明此时该lpn的子页映射可能有效,可能存在有直接可用的子页,程序会接着判断state是否>0.

若不成立只能说明此时state<0也就是子页映射全部有效(这里是因为map_entry[lpn].state是一个int类型,默认下应该是个有符号的整型取值范围,因此若state<0那么换算成二进制数值便是全1的情况,此时所有子页都是有效置位)

此时可以直接跳过以下步骤进行lsn和add_size的更新

而如果state>0则说明可能其中只是有部分子页有效,而有可能需要读取数据的子页并未存在dram和buffer中,所以需要将从SSD中写入到dram中的子页进行相应的操作,但是必须要确认到哪些子页需要进行写入。

因此程序进行进一步的确认,首先函数会调用set_entry_state()进行子页映射状态位的设置,设置后的结果保存在map_entry_new中

set_entry_state()函数会根据lsn和sub_size的参数对从lsn位置开始的子页长度为sub_size的部分标记状态位置1,代表了需要重新在map_entry[lpn].state中进行设置的子页有效映射。

接着函数会用map_entry_old保存当前dram中map_entry[lpn].state的位映射状态信息,将map_entry_newmap_entry_old进行一个按位或计算操作从而更新映射状态位,并将结果保存在modify中

随后将lpn的映射物理页pn保存至ppn中;完成映射更新后就代表了程序已经完成了该lsn在当前lpn下的读操作

随后程序会调用find_location()函数进行物理地址的查找,紧接着会统计相关的ssd信息:

更新整个ssd、当前channel和该channel下当前所在的chip中的program_count计数,表示完成一次写page操作计数;

然后将modify中保存的映射更新信息赋值给当前lsn所在的lpn对应的map_entry[lpn].state,表示已经完成了将数据写入到SSD相对应的page子页中且已经读取到了dram中

同时根据location物理地址结构体的信息设置page中的子页有效映射位valid_state也为modify以保持与dram中的同步;

且同时设置更新该page中剩余free状态的子页,接着释放并置空location。

另一方面,若map_entry[lpn].state为0说明此时该lpn对应的子页映射无效,需要程序重新分配出有效状态的物理子页给读操作请求,因此需要从SSD中先将数据写入该子页中然后以供读请求操作。

函数会调用get_ppn_for_pre_process()函数取得当前IOtrace的lsn对应的物理页号ppn,随后根据ppn调用find_location()函数取得对应的物理地址信息.

接着统计更新ssd、当前所在的channel和channel下所在的chip中的program_count计数,表示已经成功从SSD中写入数据并且读到了dram中,此时需要更新lpn对应的map_entry[lpn].pn为新获取到的ppn。

跟着程序会调用set_entry_state()函数重新设置更新子页的状态位并且将其赋值给map_entry[lpn].state

以及根据location物理地址更新lsn对应的物理page的相关参数,主要是lpn、valid_state有效状态位和free_state空闲状态位标志,最后释放并置空location。

当函数完成上述过程后,证明已经成功完成了当前IOtrace的lsn所对应的lpn读操作这时候只是读完成了至多一个page的大小,因此程序此时需要更新lsn的位置即令lsn加上已经读取完毕的sub_size大小,且同时更新已经处理的IO长度add_size

接着程序流会继续判断当前是否已经完成了该IOtrace的处理,如果未完成则重复以上的过程。当完成了该IOtrace的读请求处理后,程序会回到fgets()函数中继续读取tracefile中的下一个IO,周始反复一直到读取完所有的IOtrace。

当程序处理完tracefile中所有的读请求后,便开始打印处理完成信息,并且调用fclose()关闭tracefile文件流

随即用一个多重嵌套for循环将SSD当前状态下每一个plane的free空闲状态页的数量都按照固定格式写入到ssd->outputfile中。

每一次针对每个plane都会用fflush()函数立即将缓冲区中的数据流更新写入到outputfile文件中。当完成上述所有操作后,pre_process_page便完成了其任务,返回ssd结构体。


源码与相关注解

struct ssd_info *pre_process_page(struct ssd_info *ssd)     {    int fl=0;    unsigned int device,lsn,size,ope,lpn,full_page;     //IO操作的相关参数,分别是目标设备号,逻辑扇区号,操作长度,操作类型,逻辑页号    unsigned int largest_lsn,sub_size,ppn,add_size=0;    //最大的逻辑扇区号,子页操作长度,物理页号,该IO已处理完毕的长度    unsigned int i=0,j,k;    int map_entry_new,map_entry_old,modify;    int flag=0;    char buffer_request[200];   //请求队列缓冲区    struct local *location;     //local是物理页号相关的结构体    int64_t time;                   printf("\n");    printf("begin pre_process_page.................\n");    ssd->tracefile=fopen(ssd->tracefilename,"r");    /*以只读的模式打开trace文件从中读取IO请求*/    if(ssd->tracefile == NULL )          {        printf("the trace file can't open\n");           //若打开失败则打印错误信息并返回空指针        return NULL;    }    //full_page的值是根据逻辑子页页数N算出的2^N-1    //由代码可知,此处的full_page是代表了子页状态的屏蔽码,初始时默认设置全为1    full_page=~(0xffffffff<<(ssd->parameter->subpage_page));    /*     *计算出这个ssd的最大逻辑扇区号     *这里之所以要将总逻辑页数量乘以(1-ssd->parameter->overprovide)是由于要有一部分空间作为SSD的预留空间OP以提高SSD性能     */    largest_lsn=(unsigned int )((ssd->parameter->chip_num*ssd->parameter->die_chip*ssd->parameter->plane_die*ssd->parameter->block_plane*ssd->parameter->page_block*ssd->parameter->subpage_page)*(1-ssd->parameter->overprovide));    while(fgets(buffer_request,200,ssd->tracefile))      //逐行从tracefile文件中每次读取199字符,直到读完整个trace为止    {        sscanf(buffer_request,"%lld %d %d %d %d",&time,&device,&lsn,&size,&ope);    //读入IO的相关参数值        fl++;   //已读的IO数量计数                                                         trace_assert(time,device,lsn,size,ope);          /*断言,当读到的time,device,lsn,size,ope不合法时就会处理*/        add_size=0;          /*add_size是这个请求已经预处理的大小*/                                                          if(ope==1)           /*这里只是读请求的预处理,需要提前将相应位置的信息进行相应修改*/                                                           {            while(add_size<size)                //操作未完成之前进入循环体                                              {                                                                           lsn=lsn%largest_lsn;                  /*防止获得的lsn比最大的lsn还大*/                                                       sub_size=ssd->parameter->subpage_page-(lsn%ssd->parameter->subpage_page);                       /*                 * 这里的sub_size主要是为了定位好子请求操作位置的,这个位置是相对于某一个特定的page而言的,从这个page的第一个sub_page开始计算到这个特定的操作位置                 * 因此,sub_size其实就是从lsn扇区位置起始到该page末端的这部分内容,这部分内容是在这个page中需要被读取的。                 * 因此这里的subpage_page应该就是所谓的一个page内有多少个扇区数量,每个扇区一般都默认为512B                 */                if(add_size+sub_size>=size)                             /*只有当一个请求的大小小于一个page的大小时或者是处理一个请求的最后一个page时会出现这种情况*/                {                           sub_size=size-add_size;                                                                                                     //此处的sub_size之所以等于size-add_size原因是当请求的大小小于page时,实际上原先sub_size的区域便已经是大于实际请求大小了                    add_size+=sub_size;                                                 }                if((sub_size>ssd->parameter->subpage_page)||(add_size>size))/*当预处理一个子大小时,这个大小大于一个page或是已经处理的大小大于size就报错*/                       {                           printf("pre_process sub_size:%d\n",sub_size);                       }                /*******************************************************************************************************                *利用逻辑扇区号lsn计算出逻辑页号lpn                *判断这个dram中映射表map中在lpn位置的状态                *A,这个状态==0,表示以前没有写过,现在需要直接将sub_size大小的子页写进去                *B,这个状态>0,表示,以前有写过,这需要进一步比较状态,因为新写的状态可以与以前的状态有重叠的扇区的地方                *因此,状态==0的情况下,由于内存中映射状态无效,所以必须要重新分配出状态有效的物理页面给读操作请求,调用函数get_ppn_for_pre_process得到有效的物理页信息后                *再通过find_location()将物理页面地址信息存储并且更新该lpn对应的映射表信息。                *同样的,当状态>0时,证明该lpn位置处的内存映射表中的映射状态有效,有直接可以使用的有效物理页,所以ppn直接可以使用映射表中的pn                ********************************************************************************************************/                lpn=lsn/ssd->parameter->subpage_page;                       //这里的subpage_page初步推断应该是指每个page中的扇区数量,也就是子请求每次的操作单位                if(ssd->dram->map->map_entry[lpn].state==0)                 /*状态为0的情况,所有的页都无效状态*/                {                    /**************************************************************                    *获得利用get_ppn_for_pre_process函数获得ppn,再得到location                    *修改ssd的相关参数,dram的映射表map,以及location下的page的状态                     ***************************************************************/                    ppn=get_ppn_for_pre_process(ssd,lsn);                                      location=find_location(ssd,ppn);                    ssd->program_count++;                       ssd->channel_head[location->channel].program_count++;                       ssd->channel_head[location->channel].chip_head[location->chip].program_count++;                             ssd->dram->map->map_entry[lpn].pn=ppn;                      ssd->dram->map->map_entry[lpn].state=set_entry_state(ssd,lsn,sub_size);   //0001                    ssd->channel_head[location->channel].chip_head[location->chip].die_head[location->die].plane_head[location->plane].blk_head[location->block].page_head[location->page].lpn=lpn;                    ssd->channel_head[location->channel].chip_head[location->chip].die_head[location->die].plane_head[location->plane].blk_head[location->block].page_head[location->page].valid_state=ssd->dram->map->map_entry[lpn].state;    //获取到的读操作完成之后的子页状态                    ssd->channel_head[location->channel].chip_head[location->chip].die_head[location->die].plane_head[location->plane].blk_head[location->block].page_head[location->page].free_state=((~ssd->dram->map->map_entry[lpn].state)&full_page);                     //标识该page的所有子页都为used状态                    free(location);                    location=NULL;      //避免野指针                }//if(ssd->dram->map->map_entry[lpn].state==0)                    /*状态不为0的情况(存在子页面有效的情况)*/                {                    map_entry_new=set_entry_state(ssd,lsn,sub_size);                          /*得到新的状态,并与原来的状态相或的到一个状态*/                    map_entry_old=ssd->dram->map->map_entry[lpn].state;                       modify=map_entry_new|map_entry_old;                                     ppn=ssd->dram->map->map_entry[lpn].pn;                      location=find_location(ssd,ppn);                        ssd->program_count++;                       ssd->channel_head[location->channel].program_count++;                    ssd->channel_head[location->channel].chip_head[location->chip].program_count++;                         ssd->dram->map->map_entry[lsn/ssd->parameter->subpage_page].state=modify;                     ssd->channel_head[location->channel].chip_head[location->chip].die_head[location->die].plane_head[location->plane].blk_head[location->block].page_head[location->page].valid_state=modify;                    ssd->channel_head[location->channel].chip_head[location->chip].die_head[location->die].plane_head[location->plane].blk_head[location->block].page_head[location->page].free_state=((~modify)&full_page);                    free(location);                    location=NULL;                }                lsn=lsn+sub_size;                   /*下个子请求的起始位置*/                                                      add_size+=sub_size;                 /*已经处理了的add_size大小变化*/                                                  }        }    }       printf("\n");    printf("pre_process is complete!\n");    fclose(ssd->tracefile);    for(i=0;i<ssd->parameter->channel_number;i++)    for(j=0;j<ssd->parameter->die_chip;j++)    for(k=0;k<ssd->parameter->plane_die;k++)    {        fprintf(ssd->outputfile,"chip:%d,die:%d,plane:%d have free page: %d\n",i,j,k,ssd->channel_head[i].chip_head[0].die_head[j].plane_head[k].free_page);                        fflush(ssd->outputfile);    }    return ssd;}
0 0