標籤:
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"); //若開啟失敗則列印錯誤資訊並返回null 指標 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