首页 > 代码库 > SSDsim源码分析之pre_process_page

SSDsim源码分析之pre_process_page


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以完成读操作。

函数首先会定义一些相关的参数,其中包括了一个长度为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操作数


若为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;
}
<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    SSDsim源码分析之pre_process_page