首页 > 代码库 > 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_new和map_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;
}
SSDsim源码分析之pre_process_page