1、結構體
VFD機制中由結構體struct vfd來維護。其中各個成員變量的意義如下表所示:
fd | vfd實際對應的物理文件文件描述符 |
fdstate | FD_DELETE_AT_CLOSE:表示文件在關閉時需刪除 FD_TEMP_FILE_LIMIT:標記臨時文件 FD_CLOSE_AT_EOXACT: 這幾個都針對臨時文件 |
resowner | owner, for automatic cleanup |
nextFree | VFD的free鏈表,實際上是數組的下標。 |
lruMoreRecently | VFD的最近最少使用鏈表,爲雙向。實際上也是數組的下標 |
lruLe***ecently | lruLe***ecently爲正向,每次插入都插入頭部 |
fileSize | 文件大小 |
fileName | 文件名 |
fileFlags | 打開文件時的標籤,比如O_CREATE等 |
fileMode | 打開文件時的屬性,比如讀寫權限等 |
2、初始化
啓動時初始化,使用malloc,只在本進程中有效,即每個進程都維護各自的VfdCache而並非共享內存。初始化時只申請第一個數組,並將其fd置爲VFD_CLOSED。
PostgresMain->BaseInit->InitFileAccess: VfdCache = (Vfd *) malloc(sizeof(Vfd)); MemSet((char *) &(VfdCache[0]), 0, sizeof(Vfd)); VfdCache->fd = VFD_CLOSED; SizeVfdCache = 1;
2、open時的流程
1)Open時首先會調用AllocateVfd,從VfdCache數組中找一個空閒的slot,然後返回。該函數流程見AllocateVfd調用。
2)然後會調用ReleaseLruFiles判斷是否open了最大限制的fd。如超出限制,則將LRU鏈表最後一個VFD的fd close掉。
3)open文件,並將該VFD插入到LRU鏈表。插入LRU的函數Insert詳細流程看下面的函數分析。
4)然後對vfdP成員變量進行賦值。
PathNameOpenFilePerm-> file = AllocateVfd(); vfdP = &VfdCache[file]; ReleaseLruFiles(); vfdP->fd = BasicOpenFilePerm(fileName, fileFlags, fileMode); Insert(file); vfdP->fileName = fnamecopy; /* Saved flags are adjusted to be OK for re-opening file */ vfdP->fileFlags = fileFlags & ~(O_CREAT | O_TRUNC | O_EXCL); vfdP->fileMode = fileMode; vfdP->fileSize = 0; vfdP->fdstate = 0x0; vfdP->resowner = NULL;
AllocateVfd
1)每次調用BasicOpenFilePerm open文件前都會調用AllocateVfd從VfdCache中獲取一個空閒的vfd。
2)首先會判斷free鏈表中是否爲空。初始時刻,SizeVfdCache爲1,則會將VfdCache初始化成大小32的數組,並將其通過nextFree串聯起來形成free鏈表,注意該free鏈表爲循環。
3)VfdCache[0]不使用。最開始32個的時候,即第一次擴充後free 鏈表如下圖所示,跳過VfdCache[1],1會返回。也就是說每次取VFD都是 VfdCache[0].nextFree
4)後續再次擴充時,都是翻倍進行擴充
AllocateVfd-> if (VfdCache[0].nextFree == 0){ Size newCacheSize = SizeVfdCache * 2; if (newCacheSize < 32) newCacheSize = 32; newVfdCache = (Vfd *) realloc(VfdCache, sizeof(Vfd) * newCacheSize); VfdCache = newVfdCache; for (i = SizeVfdCache; i < newCacheSize; i++){ MemSet((char *) &(VfdCache[i]), 0, sizeof(Vfd)); VfdCache[i].nextFree = i + 1; VfdCache[i].fd = VFD_CLOSED; } VfdCache[newCacheSize - 1].nextFree = 0; VfdCache[0].nextFree = SizeVfdCache; SizeVfdCache = newCacheSize; } file = VfdCache[0].nextFree; VfdCache[0].nextFree = VfdCache[file].nextFree; return file;
ReleaseLruFiles
1)nfile爲open打開的文件數,numAllocatedDescs爲fopen打開的文件數,max_safe_fds爲操作系統計算得出的值。
2)一旦超出max_safe_fds值,就會調用ReleaseLruFile從LRU鏈表刪除一個,注意刪除的是VfdCache[0].lruMoreRecently,即鏈表的尾部,最近最少使用的。
3)首先將該fd關閉,然後將之置爲VFD_CLOSED。調用Delete函數將VFD從LRU鏈表刪除。注意這裏只是從LRU鏈表刪除,不會釋放回收到free鏈表,也不會修改vfd數據結構的其他成員變量值。因爲後續可能還會用到該物理文件,會重新open並將之重新insert到LRU鏈表。
ReleaseLruFiles-> while (nfile + numAllocatedDescs >= max_safe_fds){ if (!ReleaseLruFile()) break; }
ReleaseLruFile-> LruDelete(VfdCache[0].lruMoreRecently);-> vfdP = &VfdCache[file]; close(vfdP->fd); vfdP->fd = VFD_CLOSED; --nfile; Delete(file);--> vfdP = &VfdCache[file]; VfdCache[vfdP->lruLe***ecently].lruMoreRecently = vfdP->lruMoreRecently; VfdCache[vfdP->lruMoreRecently].lruLe***ecently = vfdP->lruLe***ecently;
3、Insert
Insert-> vfdP = &VfdCache[file]; vfdP->lruMoreRecently = 0; vfdP->lruLe***ecently = VfdCache[0].lruLe***ecently; VfdCache[0].lruLe***ecently = file; VfdCache[vfdP->lruLe***ecently].lruMoreRecently = file;
LRU鏈表的形式如下:
Insert一個VFD時:
4、Delete
Delete(file);--> vfdP = &VfdCache[file]; VfdCache[vfdP->lruLe***ecently].lruMoreRecently = vfdP->lruMoreRecently; VfdCache[vfdP->lruMoreRecently].lruLe***ecently = vfdP->lruLe***ecently;
例如刪除VfdCache[1]:
5、回收VFD
1)每次調用FileClose時,會回收vfd到free鏈表。
2)先調用close函數
3)然後將之從LRU鏈表刪除
4)如果是臨時文件,還會將臨時文件刪除
5)調用FreeVfd將vfd回收到free鏈表
FileClose-> close(vfdP->fd); --nfile; vfdP->fd = VFD_CLOSED; Delete(file); ... FreeVfd(file);
FreeVfd
調用函數FreeVfd回收,注意幾個成員變量的修改。回收時,將之插入到free鏈表頭部。注意每次取時也從頭部取
FreeVfd-> free(vfdP->fileName);//注意fileName需要釋放,他是另malloc的 vfdP->fileName = NULL; vfdP->fdstate = 0x0; vfdP->nextFree = VfdCache[0].nextFree; VfdCache[0].nextFree = file;