第11章
檔案系統
11.1儲存管理器分層結構的建立及可安裝檔案系統的載入
對於任何新裝入系統的週邊儲存設備來說,首先要做的就是將其納入系統的管理,然後才談得上對該設備的使用。從底層區塊裝置驅動程式的載入,到分區驅動程式的載入以及檔案系統驅動程式的載入,最後得以將區塊裝置及其使用的各種驅動程式納入設備管理器和儲存管理器的管理當中,這本身是一個由下往上的程序。而透過統一的檔案系統Win32 API介面定位到對應的檔案系統驅動程式,再經由分區驅動程式或直接交給設備驅動程式完成最終的檔案操作,這是一個由上往下的程序。這兩個截然相反的程序中,前者是將週邊儲存設備納入系統管理的程序,後者則是對週邊儲存設備的使用程序,對前一個程序的分析即為本章的重點。
當系統發現一個新的設備時,首先會由設備管理器負責為其載入對應的設備驅動程式,並在設備管理器中建立對應的資料結構記錄與該設備相關的各種資訊。之後如果發現新裝入的設備是區塊裝置,還要由儲存管理器為其載入檔案系統驅動程式。這時,設備管理器會將一些必要的資訊告知儲存管理器,由它來完成後面的工作。儲存管理器首先會根據註冊表中的組態資訊為區塊裝置載入分區驅動程式,分區驅動程式的主要作用就是定位區塊裝置上的分區。下面儲存管理器會為設備載入所需的檔案系統驅動程式。在這一程序中,儲存管理器會為設備上的卷(volume)註冊第一層目錄的索引,將來用戶透過全路徑名對檔進行存取時就是由其中的一級目錄名找到相應的檔案系統。
這一程序大致如下:
1) 設備管理器負責載入區塊裝置的驅動程式。
2) 由該設備發出一個
通知
关于发布提成方案的通知关于xx通知关于成立公司筹建组的通知关于红头文件的使用公开通知关于计发全勤奖的通知
,告知儲存管理器該設備的設備名及其GUID等資訊。
3) 儲存管理器根據組態資訊為該設備載入分區驅動程式。
4) 儲存管理器列舉該設備上的所有分區。
5) 儲存管理器為每個分區載入檔案系統驅動程式。
下面先對這一程序中所使用到的資料結構做一下介紹,然後再將上述儲存管理器分層結構的建立程序詳細展開,以Ramdisk的安裝程序為例看一看在實際的原始碼中這一程序的實作。
11.1.1 幾個重要的資料結構
每當系統中出現新的週邊儲存設備時,儲存管理器都會為其建立一系列的資料結構用以維護與該設備有關的資訊。這些資料結構包括CStore、DriverState和DSK等,它們都是用來描述一個設備的。對於設備上的分區,儲存管理器中也有一套用於維護分區資訊的資料結構,包括CPartition、PartState和VOL等。
CStore是一個類別,它描述了一個週邊儲存設備的一些基本資訊,由儲存管理器負責建立,其中一些成員變數的含義如下:
• m_szDeviceName:設備名稱,由設備管理器告知。
• m_DeviceGuid:設備的GUID,也是由設備管理器告知的。
• m_szRootRegKey:此設備的組態資訊在註冊表中的位置。
• m_pPartDriver:設備所使用的分區驅動程式。
• m_pPartitionList:設備上的CPartition分區串列。
• m_dwStoreId:由分區管理器維護的與此設備對應的DriverState結構的指標。
• m_pNextStore:指向g_pStoreRoot佇列中下一個CStore。
DriverState由分區管理器負責建立並維護,記錄了一些與分區管理器有關的設備細節資訊,其中一些成員變數的含義如下:
• pPartState:設備上的PartState分區串列。
• pSearchState:指向SearchState結構組成的串列。
• diskInfo:設備參數,包括CHS值、每個磁區所含的位元組數等等。
• snExtPartSector:設備上擴展分區的起始磁區。
• snExtPartEndSec:擴展分區的結束磁區。
C P a r t i t i o n也是一個類別,用於描述設備上的分區,由儲存管理器負責建立並維護,其中一些成員變數的含義如下:
• m_dwPartitionId:由分區管理器維護的與此分區對應的PartState的控制碼。
• m_dwStoreId:與此分區所在的設備對應的DriverState結構的指標。
• m_szPartitionName:分區的名稱。
• m_szFileSys:此分區所使用的檔案系統的名稱。
• m_pDsk:指向由檔案系統驅動程式負責維護的與此設備相關的DSK結構。
• m_hFSD:檔案系統驅動程式對應的動態連結程式庫的控制碼。
• m_pNextPartition:指向同一設備上的下一個CPartition物件。
P a r t S t a t e是分區管理器為維護分區資訊所使用的資料結構,與DriverState密切相關,其中一些成員變數的含義如下:
• cPartName:分區的名稱。
• snStartSector:該分區的起始磁區號。
• snNumSectors:此分區所佔用的磁區數,即分區大小。
• NextPartState:指向同一設備上的下一個PartState結構。
• pState:指向此分區所屬設備的DriverState結構。
CPartDriver是一個類別,用於描述一個分區驅動程式,其成員變數m_hPartDriver記錄了該分區驅動程式對應的動態連結程式庫的控制碼,其他的成員變數均為函數指標,與分區驅動程式的輸出函數
一一對應。
以上這些資料結構的關係如圖11 - 1所示:
圖11-1 幾個重要資料結構的關係
下面再來看一下由檔案系統驅動程式管理器負責建立並維護的FSD、DSK和VOL這三個資料結構。
FSD結構用來描述一個檔案系統驅動程式或是一個篩檢程式,其中一些成員變數的含義如下:
• hFSD:檔案系統驅動程式或篩檢程式對應的動態連結程式庫控制碼。
• pfnMountDisk:檔案系統驅動程式的輸出函數FSD_MountDisk的入口位址。
• pfnUnmountDisk:檔案系統驅動程式的輸出函數FSD_UnmountDisk的入口位址。
• pfnHookVolume:篩檢程式的輸出函數FSD_HookVolume的入口位址。
• pfnUnhookVolume:篩檢程式的輸出函數FSD_UnhookVolume的入口位址。
• szFileSysName:檔案系統或篩檢程式的名稱。
• szRegKey:檔案系統或篩檢程式的相關資訊在註冊表中的位置。
• wsFSD:檔案系統驅動程式輸出函數的前置碼(prefix)。
• apfnAFS、apfnFile和apfnFind:記錄了所有輸出函數的入口位址。
每個檔案系統驅動程式和篩檢程式除了對應一個FSD結構以外,還會對應一個DSK結構,其中一些成員變數的含義如下:
• pVo l:指向VOL結構組成的雙向串列的開頭結點。
• pFSD:指向對應的FSD結構。
• fdi :設備參數資訊,包括CHS值、每個磁區所含的位元組數等。
• pDeviceIoControl:DeviceIoControl函數的入口位址。
VOL結構用於描述一個卷(volume),也就是一個分區,其中一些成員變數的含義如下:
• pDsk:指向此卷(volume)所使用的最上層檔案系統或篩檢程式所對應的DSK結構。
• dwVolData:由FSDMGR_RegisterVolume提供的某一檔案系統驅動程式中使用的VOLUME結構的指標。
• iAFS:由FSDMGR_RegisterVolume獲得的AFS號。
11.1.2 情景分析
首先看一下Windows CE 與檔案系統相關的原始碼組織。在下面的說明中,%_WINCEROOT%代表安裝WINCE400的根目錄。
• %_WINCEROOT%\PRIVATE\WINCEOS\COREOS\FSD:三種檔案系統驅動程式的實作,包括FAT、RELFSD以及UDFS。
• %_WINCEROOT%\PRIVATE\WINCEOS\COREOS\STORAGE:儲存管理器的實作。
• %_WINCEROOT%\PRIVATE\WINCEOS\COREOS\STORAGE\DOSPART:MSPart.dll的實作。
• %_WINCEROOT%\PRIVATE\WINCEOS\COREOS\STORAGE\FSDMAIN:檔案系統驅動程式管理器的實作。
• %_WINCEROOT%\PRIVATE\WINCEOS\COREOS\STORAGE\FSDSERV:檔案系統驅動程序管理器使用的一些服務常式及其提供的API。
• %_WINCEROOT%\PRIVATE\WINCEOS\COREOS\STORAGE\STOREAPI:儲存管理器提供的API的封裝。
• %_WINCEROOT%\PRIVATE\WINCEOS\COREOS\STORAGE\STOREMGR:儲存管理器的啟動原始碼, CStore和CPartition等類別的實作。
另外,與Ramdisk相關的原始碼位於以下兩個目錄:
• %_WINCEROOT%\PUBLIC\COMMON\OAK\DRIVERS\BLOCK\RAMDISK\DRIVER:Build此目錄下的原始檔案可以得到Ramdisk的驅動程式ramdisk.dll。
• %_WINCEROOT%\PUBLIC\COMMON\OAK\DRIVERS\BLOCK\RAMDISK\LOADER:Build此目錄下的原始檔案可以得到Ramdisk的安裝程式ceramdrv.exe,該程式用於啟動Ramdisk。
下面就以Ramdisk的安裝程序為例說明當系統中出現新的週邊儲存設備時,其設備驅動程式、分區驅動程式以及檔案系統驅動程式是如何被裝入系統的,以及系統是如何透過設備管理器和儲存
管理器來管理新發現的設備。
在系統的啟動程序中會自動載入兩個模組,一個是filesys.exe,與物件儲存相關的基於RAM和基於ROM的檔案系統的功能就是由該模組實作的;另一個是fsdmgr.dll,當新的週邊儲存設備裝入系統時由它負責載入FAT這種可安裝的檔案系統。系統在裝入fsdmgr.dll這一模組時會呼叫初始化函數Init,而建立儲存管理器分層結構的入口點位於Init函數中的InitStorageManager。在InitStorageManager中,首先會呼叫CreateAPISet和RegisterAPISet註冊儲存管理器對外提供的API集合,另外負責建立g_hPNPUpdateEvent這一事件。下面透過CreateMsgQueue建立了一個訊息佇列g_hPNPQueue,由於msgopts.bReadAccess被設為TRUE,因此在Storage Manager這方面來講只能對該訊息佇列進行讀取操作。之後是啟動用於支援即插即用的執行緒PNPThread。在此執行緒中,首先由GetProcAddress得到RequestDeviceNotifications和StopDeviceNotifications這兩個函數在coredll.dll中的入口位址,然後透過呼叫RequestDeviceNotifications告知設備管理器將發現新的區塊設備的通告寫入訊息佇列g_hPNPQueue中。這些準備工作完成後PNPThread將進入一個無限迴圈,每隔一定時間對g_hPNPQueue和g_hPNPUpdateEvent進行一次輪詢。如果設備管理器發現新設備,會透過WriteMsgQueue向訊息佇列中寫入含有新設備資訊(GUID,設備名等)的DEVDETAIL結構。這時,在PNPThread執行緒中就可以透過ReadMsgQueue將此資訊讀出,並呼叫MountStore來進行下面的載入工作。
***
%_WINCEROOT%\PRIVATE\WINCEOS\COREOS\STORAGE\STOREMGR\storemain.cpp Line 608 ***
BOOL InitStorageManager()
{
DWORD dwThreadId=0;
int iAFS = INVALID_AFS;
MSGQUEUEOPTIONS msgopts;
iAFS = RegisterAFSName(L"StoreMgr");
if (iAFS != INVALID_AFS && GetLastError() == 0) {
g_hSTRMGRApi = CreateAPISet("PFSD", ARRAY_SIZE(apfnSTGMGRAPIs), (const PFNVOID
*) apfnSTGMGRAPIs, asigSTGMGRAPIs);
if (!RegisterAFSEx(iAFS, g_hSTRMGRApi, (DWORD) 1, AFS_VERSION ,AFS_FLAG_HIDDEN)) {
DEBUGMSGW(ZONE_INIT, (DBGTEXTW("STOREMGR:InitStoreMgrfailedregistering secondary volume\r\n")));
DeregisterAFSName(iAFS);
goto Fail;
}
} else {
goto Fail;
}
// 建立API集合
g_hFindStoreApi = CreateAPISet("FSTG", ARRAY_SIZE(apfnFindStoreAPIs), (const PFNVOID * )apfnFindStoreAPIs, asigFindStoreAPIs);
g_hFindPartApi = CreateAPISet("FPRT", ARRAY_SIZE(apfnFindPartAPIs), (const PFNVOID * )apfnFindPartAPIs, asigFindPartAPIs);
g_hStoreApi = CreateAPISet("STRG", ARRAY_SIZE(apfnSTGAPIs), (const PFNVOID *)apfnSTGAPIs, asigSTGAPIs);
g_hPNPUpdateEvent = CreateEvent( NULL, FALSE, FALSE, NULL);
// 建立訊息佇列,儲存管理器對該佇列只能進行讀取操作
msgopts.dwSize = sizeof(MSGQUEUEOPTIONS);
msgopts.dwFlags = 0;
msgopts.dwMaxMessages = 0; //?
msgopts.cbMaxMessage = sizeof(g_pPNPBuf);
msgopts.bReadAccess = TRUE;
g_hPNPQueue = CreateMsgQueue(NULL, &msgopts);
if (!g_hFindPartApi || !g_hFindStoreApi || !g_hStoreApi || !g_hPNPUpdateEvent || !g_hPNPQueue)
goto Fail;
// 註冊API 集
RegisterAPISet(g_hFindStoreApi, HT_FIND | REGISTER_APISET_TYPE);
RegisterAPISet(g_hFindPartApi, HT_FIND | REGISTER_APISET_TYPE);
RegisterAPISet(g_hStoreApi, HT_FILE | REGISTER_APISET_TYPE);
InitializeCriticalSection(&g_csStoreMgr);
// 啟動PNP執行緒,隨時準備載入新發現的週邊儲存設備
if (g_hPNPThread = CreateThread( NULL, 0, PNPThread, NULL, 0, &dwThreadId)) {
} else {
goto Fail;
}
return TRUE;
Fail:
CloseAllGlobalHandles();
return FALSE;
}
***
%_WINCEROOT%\PRIVATE\WINCEOS\COREOS\STORAGE\STOREMGR\storemain.cpp
Line 493 ***
DWORD PNPThread(LPVOID lParam)
{
extern const TCHAR *g_szSTORAGE_PATH; // = L"System\\StorageManager";
extern const TCHAR *g_szReloadTimeOut; // = L"UnloadDelay";
DWORD dwFlags, dwSize;
//
KEY hDevKey;
HANDLE hReg;
DEVDETAIL * pd = (DEVDETAIL *)g_pPNPBuf;
GUID guid = {0};
HANDLE pHandles[2];
TCHAR szGuid[MAX_PATH];
HMODULE hCoreDll;
DWORD dwTimeOut = INFINITE, dwTimeOutReset = DEFAULT_TIMEOUT_RESET;
PSTOPDEVICENOT pStopDeviceNotification = NULL;
PREQUESTDEVICENOT pRequestDeviceNotification = NULL;
pHandles[0] = g_hPNPQueue;
pHandles[1] = g_hPNPUpdateEvent;
HKEY hKey;
// 從註冊表[HKEY_LOCAL_MACHINE\System\StorageManager]處讀取
// PNPUnloadDelay的值
if (ERROR_SUCCESS == RegOpenKeyEx( HKEY_LOCAL_MACHINE, g_szSTORAGE_PATH, 0, 0, hKey)) {
if (!FsdGetRegistryValue(hKey, g_szReloadTimeOut, &dwTimeOutReset)) {
dwTimeOutReset = DEFAULT_TIMEOUT_RESET;
}
}
DEBUGMSG(ZONE_INIT,( L"STOREMGR:Using PNP unload delay of %ld\r\n",dwTimeOutReset));
AutoLoadFileSystems();
// 由coredll.dll中得到RequestDeviceNotifications和StopDeviceNotifications兩個函數
// 的入口地址
hCoreDll = (HMODULE)LoadLibrary(L"coredll.dll");
if (hCoreDll) {
pRequestDeviceNotification = (PREQUESTDEVICENOT)GetProcAddress( hCoreDll, L"RequestDeviceNotifications");
pStopDeviceNotification=(PSTOPDEVICENOT)GetProcAddress(hCoreDll, L"StopDeviceNotifications");
}
FreeLibrary( hCoreDll);
if (pRequestDeviceNotification && pStopDeviceNotification) {
DEBUGMSG( ZONE_INIT, (L"STOREMGR: PNPThread Created\r\n"));
while(!IsAPIReady(SH_DEVMGR_APIS)) {
Sleep(1000);
}
// 告知系統將新發現的區塊裝置的資訊寫入剛剛建立的訊息佇列g_hPNPQueue中
hReg = pRequestDeviceNotification(&BLOCK_DRIVER_GUID, g_hPNPQueue, TRUE);
while(TRUE) {
DWORD dwWaitCode;
dwWaitCode = WaitForMultipleObjects( 2, pHandles, FALSE, dwTimeOut);
if (dwWaitCode == WAIT_OBJECT_0) {
// 訊息佇列有新的訊息寫入,讀取有關新設備的資訊
if (ReadMsgQueue(g_hPNPQueue, pd, sizeof(g_pPNPBuf), &dwSize, INFINITE, &dwFlags)) {
FsdStringFromGuid(&pd->guidDevClass, szGuid);
DEBUGMSG(ZONE_INIT, (L"STOREMGR: Got a plug and play event %Class(%s) Attached=%s!!!\r\n", pd->szName, szGuid, pd->fAttached ? L"TRUE":L"FALSE"));
// 檢查新發現的設備是否為區塊裝置
if (memcmp( &pd->guidDevClass, &BLOCK_DRIVER_GUID, sizeof(GUID)) == 0) {
// 如果新的區塊裝置的狀態為Attached則繼續後面的載入工作
if (pd->fAttached) {
// 將設備名及其GUID作為參數傳給MountStore
MountStore( pd->szName, pd->guidDevClass);
⋯⋯ // 後面略去
對於Ramdisk來說,當運行其安裝程式ceramdrv.exe之後,系統便會發現這塊虛擬的週邊儲存設備並首先由設備管理器負責為其載入區塊裝置驅動程式ramdisk.dll。Ramdisk所使用的設備驅動程式等資訊是由安裝程式ceramdrv.exe填寫進註冊表的,見圖11-2,其中dll的鍵值就是Ramdisk的設備驅動程式,而IClass的鍵值{A4E7EDDA-E575-4252-9D6B-4195D48BB865}即為CE中標識區塊設備所用的GUID,它將作為參數之一傳給MountStore。另外還有很重要的一個鍵是Profile,它的鍵值指明了Ramdisk的組態資訊所在的位置,在後面還會提到這一點。
另外,在成功為其裝入區塊裝置驅動程式之後,設備管理器會將Ramdisk作為已經啟動(Active)的設備填寫進註冊表(見圖11-3),並透過WriteMsgQueue將Ramdisk的設備名及其GUID等資訊寫入訊息佇列。可以看到,與Ramdisk 有關的設備基本資訊位於[HKEY_LOCAL_MACHINE\Drivers\Builtin\Ramdisk],也就是圖11-2所示的註冊表位置;另外,Name的鍵值即為Ramdisk的設備名稱,也就是說DSK1:將作為參數之一傳給MountStore。
圖11-2 註冊表中有關Ramdisk的資訊
圖11-3 Ramdisk啟動之後的註冊表
進入MountStore之後,首先在由Storage Manager維護的以g_pStoreRoot為開頭結點的串列中尋找是否已存在具有相同設備名的store。如果發現了具有相同設備名的匹配項目,並且該store的旗標位元STORE_FLAG_DETCHED處於重導模式(說明設備可用),則表明該設備已經載入到系統中,直接返回。相反,如果在串列中未找到匹配項目,或雖然找到但是該store的旗標位元STORE_FLAG_DETACHED處於已置位元狀態(說明設備不可用),則還要繼續下面的工作。即以設備管理器告知的設備名及其GUID建立一個CStore物件,然後呼叫CStore::MountStore(BOOLbMount)完成實際的載入工作。如果以g_pStoreRoot為開頭結點的串列中存在旗標位元STORE_FLAG_DETACHED處於已置位元狀態的store,則參數bMount為FALSE,否則為TRUE。
在CStore::MountStore函數中,首先呼叫OpenDisk。由於從設備管理器那裏已經知道了設備名稱,因此可以透過CreateFileW建立設備控制碼,這之後還可以由DeviceIoControl得到有關該設備的更詳細資訊。在呼叫DeviceIoControl時如果dwIoControlCode為DISK_IOCTL_GETINFO,那麼可以得到包含CHS等設備參數的DISK_INFO結構,並將其保存在Cstore的成員變數m_si中;如果dwIoControlCode為IOCTL_DISK_DEVICE_INFO,那麼可以得到有關該設備的一些額外資訊,這些資訊組成了一個STORAGEDEVICEINFO結構,並且保存在CStore的成員變數m_sdi中。STORAGEDEVICEINFO結構中很重要的一項資訊就是該設備的Profile在註冊表中的位置,這決定了將從哪裡得到有關該設備的組態資訊。之後就可以呼叫GetStoreSettings,由註冊表的Profile中讀出該設備的這些組態資訊。例如,是否支援AUTOMOUNT、AUTOFORMAT以及AUTOPART、該設備所使用的預設檔案系統、為該設備載入哪個partition driver等。得到這些信息後,首先建立一個CPartDriver物件,並透過LoadPartitionDriver載入從註冊表中得知的該設備所使用的partition driver,另外LoadPartitionDriver還會完成剛剛載入的partition driver中的輸出函數到CPartDriver中函數指標的映射。如果在註冊表中未提供有關partition driver的載入資訊,則系統使用預設的partition driver(MSPart.dll)。下面呼叫的OpenStore被映射到剛剛載入的partition driver的PD_OpenStore,它會根據由設備啟動磁區讀入的磁片分區表建立並維護PartitionManager將用到的DriverState(代表設備)、PartState(代表設備上的分區)和SearchState(用於遍曆(iterate)設備上的分區)等資料結構。該函數返回時, m_dwStoreId已被填寫為在Partition Manager這一層上與設備相對應的DriverState結構的指標。之後的GetStoreInfo被映射為PD_GetStoreInfo,由於剛剛得到的m_dwStoreId是指向DriverState結構的指標,因此可以從Partition Manager保存的資料結構中讀出這裏所需的資訊,並把這些資訊組織在一個PD_STOREINFO結構中。接下來檢查m_dwStoreId所指向的設備是否已經進行過格式化操作,如果該設備尚未被格式化並且前面由註冊表讀出的組態資訊表明該設備支援AUTOFORMAT,則呼叫被映射為PD_FormatStore的FormatStore函數對該設備進行自動格式化。而後的GetPartitionCount透過PD_FindPartitionStart、PD_FindPartitionNext以及PD_FindPartitionClose這三個函數的配合對該設備的分區進行遍曆(iterate),由此統計出設備上的分區個數。如果統計結果為零,說明該設備尚未進行分區。這時如果前面由註冊表讀出的組態資訊表明該設備支援AUTOPART,則以設備上可供利用的最大儲存空間自動建立並格式化一個分區,這是透過呼叫CreatePartition和FormatPartition這兩個函數完成的。最後一
步是呼叫LoadExistingPartitions,它會像GetPartitionCount一樣藉由所載入的partition driver的PD_FindPartitionStart、PD_FindPartitionNext以及PD_FindPartitionClose對該設備的所有分區進行巡訪,但這次巡訪的目的不是為了統計分區個數,而是呼叫LoadPartition依次載入每個分區。
***
%_WINCEROOT%\PRIVATE\WINCEOS\COREOS\STORAGE\STOREMGR\store.cpp
Line 322
***
DWORD CStore::MountStore(BOOL bMount)
{
DWORD dwError = ERROR_GEN_FAILURE;
// 由設備名稱DSK 1 :建立Ramdisk的設備識別碼,並得到設備的相關資訊
if(ERROR_SUCCESS == (dwError = OpenDisk()))
// 由註冊表得到Ramdisk的組態資訊
if (GetStoreSettings()) {
m_pPartDriver = new CPartDriver();
// 裝入預設的分區驅動程式mspart.dll
if (ERROR_SUCCESS == (dwError = m_pPartDriver-> LoadPartitionDriver (m_szPartDriver))) {
// 由mspart.dll中的PD_OpenStore為Ramdisk在Partition Manager建立
// 相應的資料結構
if (ERROR_SUCCESS == (dwError = m_pPartDriver- > OpenStore (
m_hDisk, & m_dwStoreId ) ) ) {
DEBUGMSG( 1, (L" Opened the store hStore=%08X\r\n",
m_dwStoreId ) ) ;
m_si.cbSize = sizeof(PD_STOREINFO);
m_pPartDriver->GetStoreInfo( m_dwStoreId, &m_si);
DEBUGMSG( 1, ( L " NumSec = %ld BYTEsPerSec= %ld FreeSec = %ld
BiggestCreatable=%ld\r\n", (DWORD)m_si.snNumSectors, (DWORD)m_si.dwBytesPerSector, (DWORD)m_si.snFreeSectors, (DWORD)m_si.snBiggestPartCreatable));
// 檢查Ramdisk是否已經進行過格式化操作,如果尚未進行過格式化
// 並且由註冊表得知Ramdisk支持AUTOFORMAT,則對其進行自動// 格式化操作
if (ERROR_SUCCESS != m_pPartDriver->IsStoreFormatted(m_dwStoreId))
{ // TODO: Look for ERROR_BAD_FORMAT
if (m_dwFlags & STORE_ATTRIBUTE_AUTOFORMAT) {
// TODO: Check for failure
m_pPartDriver- > FormatStore( m_dwStoreId ) ;
m_pPartDriver->GetStoreInfo( m_dwStoreId, &m_si);
}
}
// 藉由分區驅動程式提供的PD_FindPartitionStart、
// PD_FindPartitionNext 和PD_FindPartitionClose三個函數統計分區個// 數
GetPartitionCount() ;
// 如果統計結果為零,說明設備尚未分區。這時根據註冊表中AutoPart
// 的鍵值決定是否自動分區
if (!m_dwPartCount) {
if (m_dwFlags & STORE_ATTRIBUTE_AUTOPART) {
PD_STOREINFO si;
si.cbSize = sizeof(PD_STOREINFO);
m_pPartDriver->GetStoreInfo(m_dwStoreId, &si);
if ( (ERROR_SUCCESS == m_pPartDriver->CreatePartition(m_dwStoreId, L"PART00", 0, si.snBiggestPartCreatable, TRUE)) &&
(ERROR_SUCCESS == m_pPartDriver -> FormatPartition
(m_dwStoreId, L"PART00", 0, TRUE))) {
m_pPartDriver -> GetStoreInfo( m_dwStoreId, &m_si ) ;
DEBUGMSG( 1, (L"NumSec=%ld BytesPerSec=%ld
FreeSec = %1dBiggestCreatable= %ld\r\n " , ( DWORD) m_si.snNumSectors, (DWORD) m_si.dwBytesPerSector,(DWORD)m_si.snFreeSectors,(DWORD)m_si.snBiggestPartCreatable ));
m_dwPartCount = 1;
}
}
}
// 呼叫LoadExistingPartitions完成後面的工作
LoadExistingPartitions ( bMount ) ;
m_dwFlags |= STORE_FLAG_MOUNTED;
}
}
}
}
return dwError;
}
***
%_WINCEROOT%\PRIVATE\WINCEOS\COREOS\STORAGE\STOREMGR\store.cpp
Line 178 ***
DWORD CStore::OpenDisk()
{
DWORD dwError = ERROR_SUCCESS;
// 根據設備名建立設備識別碼,首先嘗試以可讀可寫方式建立
m_hDisk = CreateFileW(m_szDeviceName,
GENERIC_READ | GENERIC_WRITE,
0 ,
NULL, OPEN_EXISTING, 0, NULL);
if (m_hDisk == INVALID_HANDLE_VALUE) {
dwError = GetLastError();
// 如果嘗試失敗則以只讀方式建立
if (dwError == ERROR_ACCESS_DENIED) {
dwError = ERROR_SUCCESS;
m_hDisk = CreateFileW(m_szDeviceName,
GENERIC_READ,
FILE_SHARE_READ ,
NULL, OPEN_EXISTING, 0, NULL);
if (m_hDisk != INVALID_HANDLE_VALUE) {
m_dwFlags |= STORE_ATTRIBUTE_READONLY;
} else {
dwError = GetLastError();
}
}
}
if (m_hDisk != INVALID_HANDLE_VALUE) {
DWORD dwRet;
// 得到含有C H S值的DISK _ INFO資訊
if ( !::DeviceIoControl ( m_hDisk,DISK_IOCTL_GETINFO, & m_si ,
sizeof(DISK_INFO) , NULL, 0, & dwRet, NULL)) {
dwError = GetLastError();
if ( ( dwError ==ERROR_BAD_COMMAND) || ( dwError ==
ERROR_NOT_SUPPORTED ) ) {
memset( &m_si, 0, sizeof(DISK_INFO));
dwError = ERROR_SUCCESS;
}
}
if (dwError == ERROR_SUCCESS) {
// 得到STORAGEDEVICEINFO資訊,其中m_sdi. szProfile指明了設備的
// 組態資訊在註冊表中的位置
if ( !::DeviceIoControl ( m_hDisk , IOCTL_DISK_DEVICE_INFO , &m_sdi,
sizeof (STORAGEDEVICEINFO ), NULL, 0, &dwRet, NULL)) {
DEBUGMSG( ZONE_INIT, (L"FSDMGR!CStore::OpenDisk(0x%08X) call to
IOCTL_DISK_DEVICE_INFO failed..filling info\r\n", this));
m_sdi.dwDeviceClass = STORAGE_DEVICE_CLASS_BLOCK;
m_sdi.dwDeviceFlags = STORAGE_DEVICE_FLAG_READWRITE;
m_sdi.dwDeviceType = STORAGE_DEVICE_TYPE_UNKNOWN;
wcscpy( m_sdi.szProfile, L"Default");
}
}
DEBUGMSG( ZONE_INIT, (L"FSDMGR!CStore::OpenDisk(0x%08X) DeviceInfo Class(0x%08X) Flags(0x%08X) Type(0x%08X) Profile(%s)\r\n",
this,
m_sdi.dwDeviceClass ,
m_sdi.dwDeviceFlags ,
m_sdi.dwDeviceType ,
m_sdi.szProfile ) ) ;
}
return dwError;
}
***%_WINCEROOT%\PRIVATE\WINCEOS\COREOS\STORAGE\STOREMGR\store.cpp
Line 121 ***
BOOL CStore::GetStoreSettings()
{
HKEY hKeyStorage=NULL, hKeyProfile = NULL;
BOOL bRet = FALSE;
// 將hKeyStorage值為[ HKEY_LOCAL_MACHINE\System\StoageManager\Profiles ]
if (ERROR_SUCCESS == RegOpenKeyEx( HKEY_LOCAL_MACHINE, g_szPROFILE_PATH, 0, 0, &hKeyStorage ) ) {
DUMPREGKEY(ZONE_INIT, g_szPROFILE_PATH, hKeyStorage);
// 將hKeyProfile值為
// [HKEY_LOCAL_MACHINE\System\StorageManager\Profiles\RamDisk]
wsprintf( m_szRootRegKey, L"%s\\%s", g_szPROFILE_PATH, m_sdi.szProfile);
if (ERROR_SUCCESS != RegOpenKeyEx (HKEY_LOCAL_MACHINE, m_szRootRegKey, 0, 0, &hKeyProfile)) {
hKeyProfile = NULL;
} else {
DUMPREGKEY(ZONE_INIT, m_sdi.szProfile, hKeyProfile);
}
// 根據註冊表中A U T O MOUNT、AUTOFORMAT以及AUTOPART三者的值設
// 值m_dwFlags
if (!hKeyProfile || !FsdLoadFlag(hKeyProfile, g_szAUTO_MOUNT_STRING,
&m_dwFlags, STORE_ATTRIBUTE_AUTOMOUNT ) )
if (!FsdLoadFlag(hKeyStorage, g_szAUTO_MOUNT_STRING, &m_dwFlags,
STORE_ATTRIBUTR_AUTOMOUNT ) )
m_dwFlags |= STORE_ATTRIBUTE_AUTOMOUNT;
if (!hKeyProfile || !FsdLoadFlag(hKeyProfile, g_szAUTO_FORMAT_STRING,
&m_dwFlags, STORE_ATTRIBUTE_AUTOFORMAT))
Fsd_LoadFlag ( hKeyStorage , g_szAUTO_FORMAT_STRING , &m_dwFlags ,
STORE_ATTRIBUTE_ AUTOFORMAT ) ;
if (!hKeyProfile || !FsdLoadFlag(hKeyProfile, g_szAUTO_PART_STRING,
&m_dwFlags, STORE_ATTRIBUTE_AUTOPART))
Fsd_LoadFlag ( hKeyStorage , g_szAUTO_PART_STRING, &m_dwFlags ,
STORE_ATTRIBUTE_ AUTOPART ) ;
// 根據註冊表得到Ramdisk所使用的文件系統驅動程式,最後使用的一級目錄名
// 稱以及儲存設備的描述性名稱
if (!hKeyProfile || !FsdGetRegistryString (hKeyProfg_szFILE_SYSTEM_STRING,m_szDefaultFileSystem,sizeof(m_szDefaultFileSystem)))
if (!FsdGetRegistryString(hKeyStorage, g_szFILE_SYSTEM_STRING,
m_szDefaultFileSystem, sizeof(m_szDefaultFileSystem ) ) )
wcscpy( m_szDefaultFileSystem, g_szDEFAULT_FILESYSTEM);
if ( ! hKeyProfile | | !FsdGetRegistryString ( hKeyProfg_szFOLDER_NAME_STRING, m_szFolderName, sizeof(m_szFolderName ) ) )
if (!FsdGetRegistryString(hKeyStorage, g_szFOLDER_NAME_STRING,
m_szFolderName, sizeof(m_szFolderName)))
wcscpy( m_szFolderName, g_szDEFAULT_FOLDER_NAME);
if ( ! hKeyProfile || ! FsdGetRegistryString ( hKeyProfg_szSTORE_NAME_STRING, m_szStoreName, sizeof(m_szStoreName)))
if ( ! FsdGetRegistryString( hKeyStorage , g_szSTORE_NAME_STRING,
m_szStoreName, sizeof(m_szStoreName)))
wcscpy( m_szStoreName, g_szDEFAULT_STORAGE_NAME);
}
// 從註冊表中讀取Ramdisk所使用的分區驅動程式名稱
if (!GetPartitionDriver(hKeyStorage, hKeyProfile))
goto ExitFalse;
bRet = TRUE;
ExitFalse :
if (hKeyStorage) {
RegCloseKey( hKeyStorage);
}
if (hKeyProfile) {
RegCloseKey( hKeyProfile);
}
return bRet;
}
由圖11-2可知,Profile鍵值為Ramdisk,正是這個值指出Ramdisk的組態資訊在註冊表的位置,即[HKEY_LOCAL_MACHINE\System\StoageManager\Profiles\Ramdisk],此處所
含的註冊表資訊如圖11 - 4所示。
由圖11 - 4可知,Ramdisk支持Autoformat、AutoPart和A u t o Mount三種操作,使用的文件系統驅動程式為FATFS,在後面註冊時使用的一級目錄名為Storage Card。另外對於此處沒有給出的一些資訊則要用到系統的預設組態,這些預設組態資訊位於[HKEY_LOCAL_MACHINE\System\StoageManager\Profile],見圖11 - 5。這裡Ramdisk用到的惟一一項預設組態就是分區驅動程式mspart.dll。
下面再來看看LoadPartition。首先是呼叫OpenPartition,藉由該函數得到由Partition Manager維護的代表該分區的PartState結構的指標。在這之後,建立一個CPartition對象並將其加入到CStore的m_pPartitionList串列中,然後呼叫LoadPartition得到有關該分區的具體資訊(匯總為一個PD_PARTINFO結構)。之後是一個判斷語句,如果該store代表一個新發現的設備並且前面由
圖11-4 註冊表中保存的Ramdisk的組態資訊
圖11-5 儲存管理器可能會用到的預設組態資訊
註冊表讀出的組態資訊表明該設備支持AUTOMOUNT,則呼叫MountPartition。其參數m_szRootRegKey為該設備的Profile在註冊表中的位置,另一個參數則是預設使用的文件系統名稱。在函數MountPartition中,首先由註冊表讀出有關分區的Mount選項以及所使用的文件系統等資訊,然後裝入相應文件系統驅動程式的動態連結庫。最後填寫一個FSDINTDATA資料結構,並呼叫InitEx。下面的工作就交由FSD Manager來完成了。
*** %_WINCEROOT%\PRIVATE\WINCEOS\COREOS\STORAGE\STOREMGR\store.cpp Line 291 ***
BOOL CStore::LoadPartition(LPCTSTR szPartitionName, BOOL bMount)
{
DWORD dwPartitionId;
// 得到由Partiton Manager負責維護的PartState結構的指標
if (ERROR_SUCCESS == m_pPartDriver->OpenPartition(m_dwStoreId,
szPartitionName,&dwPartitionId)) {
// 建立CPartition對象並將其加入到CStore的m_pPartitionList串列中
CPartition *pPartition = new CPartition(m_dwStoreId, dwPartitionId,
m_pPartDriver, m_szFolderName);
AddPartition( pPartition);
// 得到有關該分區的具體資訊
pPartition -> LoadPartition (szPartitionName ) ;
// 如果前面由註冊表讀出的組態資訊表明該設備支持A U T O MOUNT,則呼叫
// CPartition的成員函數MountPartition完成後面的工作
if (bMount && (m_dwFlags & STORE_ATTRIBUTE_AUTOMOUNT)) {
if(pPartition->MountPartition(m_szRootRegKey,m_szDefaultFileSystem)){
m _ d w Mount C o u n t + + ;
}
}
return TRUE;
}
return FALSE;
}
*** %_WINCEROOT%\PRIVATE\WINCEOS\COREOS\STORAGE\STOREMGR\store.cppLine 291 ***
BOOL CPartition::MountPartition(LPCTSTR szRootRegKey, LPCTSTR szDefaultFileSystem)
{
extern const TCHAR *g_szPART_TABLE_STRING;
extern const TCHAR *g_szFILE_SYSTEM_MODULE_STRING;
extern const TCHAR *g_szMOUNT_FLAGS_STRING;
TCHAR szRegKey[MAX_PATH];
TCHAR szPartId[10];
HKEY hKey = NULL;
DWORD dwFlags = 0;
// 從註冊表[HKEY_LOCAL_MACHINE\System\StoageManager\Profiles\Ramdisk]
// 處得到Mount F l a g s的值
wcscpy( m_szRootKey, szRootRegKey);
if (ERROR_SUCCESS == RegOpenKeyEx( HKEY_LOCAL_MACHINE, m_szRootKey, 0, 0,
&hKey)) {
if(!FsdGetRegistryValue(hKey,g_szMOUNT_FLAGS_STRING,&dwFlags)) {
dwFlags = 0;
}
RegCloseKey( hKey);
}
wsprintf( szRegKey, L"%s\\%s", m_szRootKey, g_szPART_TABLE_STRING);
// 決定Ramdisk所使用的文件系統驅動程式,即FATFS
wcscpy( m_szFileSys, szDefaultFileSystem);
if (ERROR_SUCCESS == RegOpenKeyEx( HKEY_LOCAL_MACHINE, szRegKey,0, 0,&hKey)){
DUMPREGKEY(ZONE_INIT, szRegKey, hKey);
wsprintf(szPartId, L"%02X", m_pi.bPartType);
if ( ! FsdGetRegistryString( hKey , szPartId , m_szFileSys ,
sizeof(m_szFileSys))) {
wcscpy( m_szFileSys, szDefaultFileSystem);
}
}
//從註冊表[HKEY_LOCAL_MACHINE\System\StorageManager\ FATFS]處得到FAT
// 文件系統驅動程式的模組名FATFSd.dll
if (wcslen(m_szFileSys)) {
GetFSDString ( g_szFILE_SYSTEM_MODULE_STRING , m_szModuleName,
sizeof ( m_szModuleName) ) ;
GetFSDString ( g_szFILE_SYSTEM_MODULE_STRING , m_szFriendlyName ,
sizeof ( m_szFriendlyName ) ) ;
if (m_szModuleName) {
// 裝入FAT文件系統驅動程式FATFSd.dll
m_hFSD = LoadDriver( m_szModuleName);
// 填寫FSD I N I T D A T A資料結構,呼叫InitEx
if (m_hFSD) {
FSDINITDATA fid;
fid.pIoControl = (PDEVICEIOCONTROL)PartitionIoControl;
fid.hDsk = (HANDLE)this;
fid.dwFlags = dwFlags;
wcscpy( fid.szFileSysName, m_szFileSys);
wcscpy( fid.szRegKey, szRootRegKey);
wcscpy( fid.szDiskName, m_szPartitionName);
fid.hFSD = m_hFSD;
fid.pNextDisk = NULL;
m_pDsk = InitEx( &fid);
}
}
}
if (m_pDsk) {
m_dwFlags |= PARTITION_ATTRIBUTE_MOUNTED;
} else {
if (m_hFSD) {
FreeLibrary( m_hFSD);
m_hFSD = NULL;
}
}
if (hKey) {
RegCloseKey(hKey) ;
}
return TRUE;
}
進入InitEx之後首先會呼叫FSDLoad,由其完成兩項任務:
■ 呼叫AllocFSD完成FSD結構的分配及初始化,並將其納入文件系統驅動程式管理器的管理當中。其參數hFSD是裝入文件系統驅動程式所對應的動態連結庫後得到的識別碼,有了這個識別碼就可以藉由呼叫GetModuleFileName得到該動態連結庫的名稱。由於文件系統驅動程式所對應的動態連結庫的名稱與該文件系統驅動程式的輸出函數所使用的前綴名相同,因此剛剛得到的動態連結庫的名稱也就是所有輸出函數所使用的前綴名。下面藉由LocalAlloc申請分配一個FSD結構,並將其插入到文件系統驅動程式管理器負責維護的dlFSDList雙向串列中。之後是透過呼叫GetFSDProcAddress檢查hFSD對應的動態連結庫是否帶有FSD_MountDisk和FSD_UnMountDisk這兩個函數輸出點,如果有則說明這是一個文件系統驅動程式;否則繼續呼叫GetFSDProcAddress 檢查該動態連結庫是否帶有FSD _ HookVolume和FSD_UnhookVolume這兩個函數輸出點,如果有則說明這是一個過濾器驅動程式,如果還未找到則將剛剛申請的FSD結構從dlFSDList雙向串列中刪除,並釋放該結構所佔用的空間。在通過了上述檢查之後,由於前面已經知道了該文件系統驅動程式的輸出函數所使用的前綴名,因此可以透過GetFSDProcArray得到該文件系統驅動程式和過濾器驅動程式的所有輸出函數的入口地址。至此就完成了一個FSD結構的初始化工作,並且已經將其納入文件系統驅動程式管理器負責維護的dlFSDList雙向串列中。
· 呼叫AllocDisk為該FSD分配並初始化一個DSK結構。DSK結構中的hDSK識別碼由呼叫CreateFileW得到。在呼叫CreateFileW時首先試探以可讀可寫方式打開,如失敗則以只讀方式打開。由此也確定了DSK結構中的dwFlags是否具有DSK_READONL屬性。之後透過LocalAlloc申請分配一個DSK結構並完成其成員變數的初始化。其中成員變數pFSD的值是
剛剛透過AllocFSD得到的,這樣DSK與FSD這兩個結構之間就建立了連接。
*** PRIVATE\WINCEOS\COREOS\STORAGE\FSDMAIN\Fsdmain.cpp Line 155 ***
PDSK InitEx(FSDINITDATA *pInitData)
{
BOOL bSuccess = TRUE;
PDSK pDsk = FSDLoad(pInitData); // 呼叫FSDLoad建立的_DSK(設備)
// 和_ FSD(FSD模組)結構
PDSK pDskNew = HookFilters( pDsk, pDsk->pFSD);//在FSD之上掛接Filters
PFSD pFSD = pDsk->pFSD;
//指向FSDLoad中建立的那個_FSD結構
// 如果_ FSD和_DSK均建立成功
if (pFSD && pDsk) {
if (pFSD->pfnMountDisk) {
// 呼叫X X X _ Mount Disk把設備中的各個VOLUME註冊進來
if (pFSD->pfnMountDisk(pDsk)) {
// 修改_DSK的狀態
UnmarkDisk(pDsk, DSK_UNCERTAIN);
bSuccess = TRUE;
}
}
}
// 如果建立失敗,釋放前面申請的記憶體資源,略去. . .
// 返回_DSK結構的指標
return (pDsk);
}
*** PRIVATE\WINCEOS\COREOS\STORAGE\FSDMAIN\Fsdmain.cpp Line 127 ***
PDSK FSDLoad(FSDINITDATA * pInitData)
{
PFSD pFSD;
PDSK pDsk;
// 透過AllocFSD建立_ FSD結構
pFSD = AllocFSD((HMODULE)pInitData->hFSD);
// 透過AllocDisk建立DSK 結構
pDsk = AllocDisk(pFSD, pInitData->szDiskName, pInitData->hDsk,
pInitData->p IoControl ) ;
// 如果_DSK結構建立成功,繼續設置
if (pDsk){
FSDMGR_GetDiskInfo(pDsk, &pDsk->fdi);
// 獲取設備硬件方面的資訊
//(容量,磁區大小等等)
pDsk->dwFlags |= pInitData->dwFlags << 16;
// 設置_DSK的屬性
pDsk->pNextDisk = pInitData->pNextDisk; // 其實是NULL
pDsk->pPrevDisk = NULL;
}
// 如果_ FSD結構建立成功,繼續設置
if (pFSD) {
// 拷貝名稱(其實現在是NULL)
wcscpy( pFSD->szFileSysName, pInitData->szFileSysName);
// 拷貝名稱(其實現在是NULL)
wcscpy( pFSD->szRegKey, pInitData->szRegKey);
}
// 返回DSK 結構的指標
return pDsk;
}
*** PRIVATE\WINCEOS\COREOS\STORAGE\FSDSERV\Fsdalloc.cpp Line 37 ***
PFSD AllocFSD(
HMODULE
h FSD
// FSD模組的識別碼
)
{
PFSD pFSD;
// 用於記錄這個FSD模組中標準函數介面的前綴
WCHAR baseName[MAX_FSD_NAME_SIZE];
// 如果模組中FSD_CreateFileW 這個函數存在,那麼前綴很簡單,就是FSD_,這是
// 一種特殊情況
if (GetProcAddress(hFSD, L"FSD_CreateFileW")) {
wcscpy(baseName, L"FSD_");
} else {
// 否則就會比較麻煩才能找到這個前綴,略過不看. . .
}
// 函數前綴被存放在了b a s e N a m e中
// 首先,為新建的_ FSD分配記憶體空間
pFSD = (PFSD)LocalAlloc(LPTR, sizeof(FSD));
if (pFSD) {
// 設定SIGFIELD(就是把後一個值賦給SIGFIELD)
INITSIG(pFSD, FSD_SIG);
// 把新建的_ FSD結構,加入全局的雙向串列,串列頭是dlFSDList
AddListItem((PDLINK)&dlFSDList, (PDLINK)&pFSD->dlFSD);
// 設定_ FSD中模組識別碼的屬性
pFSD->hFSD = hFSD;
// 把b a s e N a m e拷貝到_ FSD中去
wcscpy( pFSD->wsFSD, baseName);
}
// 有了名稱前綴,就能透過GetProcAddress獲得模組中函數的指標
// 這裡的GetFSDProcAddress本質上還是呼叫了GetProcAddress,只不過
// 先把後邊的(如Mount Disk)和前綴合併起來,再去呼叫
if (pFSD) {
if (!pFSD->pfnMountDisk || !pFSD->pfnUnmountDisk) {
// 給_ FSD中的函數指標屬性賦值
pFSD->pfnMountDisk = (PFNMOUNTDISK)GetFSDProcAddress ( p
TEXT ( " Mount Disk " ) ) ;
pFSD - > pfnUnMount Disk = (PFNMOUNTDISK) GetFSDProcAddress ( pFSD
TEXT("UnMountDisk")) ;
}
}
// 在_ FSD結構中有三個函數指標的數組,而GetFSDProcArray就是給這個數組中的
// 函數指標賦值,其本質上還是去呼叫GetProcAddress,沒有什麼特別的地方
if ( GetFSDProcArray ( pFSD , pFSD - > apfnAFS , apfnAFSStubs ,
apwsAFSAPIs ,ARRAY_SIZE(apwsAFSAPIs)) &&
GetFSDProcArray ( pFSD , pFSD->apfnFile, apfnFileStubs, apwsFileAPIs , ARRAY_SIZE(apwsFileAPIs)) &&
GetFSDProcArray ( pFSD , pFSD->apfnFind, apfnFindStubs, apwsFindAPIs , ARRAY_SIZE(apwsFindAPIs))) {
pFSD -> pRefreshVolume = ( pRefreshVolume ) GetFSDProcAddress
L"RefreshVOLUME");
// 如果獲取函數指標失敗,透過D e AllocFSD釋放_ FSD結構(並從串列中取出)
} else {
DeAllocFSD ( pFSD ) ;
pFSD = NULL;
}
// 返回新建立的_ FSD結構的指標(如果建立失敗,返回NULL)
return pFSD;
}
*** PRIVATE\WINCEOS\COREOS\STORAGE\FSDSERV\Fsdalloc.cpp Line 159 ***
PDSK AllocDisk(
PFSD pFSD,
// 這個設備所需要的FSD模組對應的_ FSD
// 結構指標
PCWSTR pwsDsk,
// 惟一標識的設備名稱(比如說C O M 1 :)
HANDLE hDsk,
// 在InitEx中呼叫時傳遞的是
// INVALID_HANDLE_VALUE
PDEVICEIOCONTROLp IoControl // 用於IoControl的函數指標
)
{
DWORD dwFlags = DSK_NONE;
// 如果前面_ FSD建立失敗,直接錯誤退出
if (pFSD == NULL)
return NULL;
// 透過設備的惟一名稱,呼叫CreateFile打開設備
if (hDsk == INVALID_HANDLE_VALUE) {
hDsk = CreateFileW(
pwsDSK , // 設備的惟一標識名稱
GENERIC_READ | GENERIC_WRITE, // 以讀寫模式打開
0 ,
NULL,
OPEN_EXISTING, // 設備是否存在
0 ,
NULL ) ;
// 如果成功,可以進行讀寫操作,設定dwFlag
if (hDsk != INVALID_HANDLE_VALUE)
// 本設備可以進行讀寫操作(而不是只讀)
dwFlags &= ~DSK_READONLY;
// 否則的話,以只讀模式打開設備
else if (GetLastError() == ERROR_ACCESS_DENIED) {
hDsk = CreateFileW(
pwsDSK ,
GENERIC_READ,
FILE_SHARE_READ ,
// 只讀模式
NULL,
OPEN_EXISTING,
0,
NULL ) ;
// 如果成功,說明設備是只讀的
if (hDsk != INVALID_HANDLE_VALUE)
// 設備只可以進行讀操作
dwFlags |= DSK_READONLY;
}
pwsDsk = NULL;
}
// 如果讀操作都不可以,直接錯誤退出
if (hDsk == INVALID_HANDLE_VALUE) {
return NULL;
}
// 申請_DSK結構需要的記憶體空間
PDSK pDsk = (PDSK)LocalAlloc(LPTR, sizeof(DSK));
// 接下來開始設置_DSK結構的各個屬性
if (pDsk) {
INITSIG(pDsk, DSK_SIG);
// 設置SIGFIELD
pDsk->pFSD = pFSD;
// 指向前面建立的_FSD的結構
pDsk->pDeviceIoControl = pIOControl;
// 設定用於IoControl的函數指標
pDsk->pVol = NULL;
// 尚未向FSD Manager註冊本設備
// 其中的文件系統卷,現在空
// 把本設備的標識名稱拷貝到_DSK中
if (pwsDsk) {
wcscpy( pDsk->wsDsk, pwsDsk);
} else {
wcscpy( pDsk->wsDsk, L"");
}
}
// 如果為_DSK分配空間失敗,關閉前面打開的設備識別碼(代碼一定要嚴謹)
else {
if (hDsk) {
CloseHandle ( hDSK) ;
}
}
if (pDsk) {
pDsk->hDsk = hDsk;
// 設定設備的識別碼
pDsk->dwFlags = dwFlags;
// 設定設備的屬性(現在只有只讀,或者讀寫兩
// 種屬性)
}
// 最後,返回新建立的_DSK結構的指標
return pDsk;
}
FSDLoad返回之後透過呼叫FSD MGR_GetDiskInfo以及FSDINTDATA結構中尚未用到的一些資訊完成FSD和DSK兩個結構的剩餘初始化工作,例如所使用的文件系統的名稱、設備的組態資訊在註冊表中的位置等。在這之後, FSDLoad成功返回。
這之後是呼叫HookFilters從註冊表中查找所有與該文件系統驅動程式相關的過濾器,並將它
們納入文件系統驅動程式管理器的管理之中。如前所述,有關文件系統過濾器的資訊也是保存在註冊表中的,文件系統驅動程式管理器需要到以下各處查找應載入的文件系統過濾器:
[HKEY_LOCAL_MACHINE \ System\ StorageManager \AutoLoad \ FileSystem\ Filters ]
[HKEY_LOCAL_MACHINE\System\StorageManager\Profiles\ProfileName\FileSystem\Filters]
[HKEY_LOCAL_MACHINE\System\StorageManager\FileSystem\Filters]
[ HKEY_LOCAL_MACHINE \ System\ StorageManager \ Filters ]
HookFilters的第一項工作就是到以上各處查找需要載入的過濾器驅動程式,並透過呼叫LoadFSD List將所有的過濾器驅動程式連結成一個單向佇列。註冊表中與每個過濾器相關的組態資訊中都有一個Order,由它決定向文件系統驅動程式載入這些過濾器驅動程式時所採用的順序。與此對應,LoadFSDList函數也有一個參數bReverse用於表明連結時所採用的順序。如果為TRUE,則LoadFSDList在查找到過濾器驅動程式後將它們按遞增排列。在收集了所有有關過濾器的資訊
之後,下面要做的就是將所有的過濾器驅動程式一一連結到文件系統驅動程式上。這也是透過調用FSDLoad完成的,只不過前面載入的對象是文件系統驅動程式,而這次是一些過濾器驅動程式。由於每次都是從LoadFSDList建立的pFSDLoadList開頭結點摘下一個過濾器,因此連接完成之後這些過濾器驅動程式的順序便成了遞減(REVERSE)。這一過程如圖11-6、圖11-7所示,所有的過濾
器驅動程式構成了過濾器驅動程式堆疊。
最後要做的是呼叫文件系統驅動程式的輸出函數FSD_MountDisk,由其負責定位新設備上的所有卷(Volume),並呼叫FSDMGR_RegisterVolume在文件系統驅動程式管理器中對它們進行註冊。在每個文件系統驅動程式中一般還會有一套由其自己定義的DSK、VOLUME及其他一些結構,這些結構所代表的對象與文件系統驅動程式管理器中定義的DSK、VOL等結構相同,但一般來說更為具體,以適應不同文件系統驅動程式的具體需要。FSD_MountDisk的主要作用就在於配合文件系統驅動程式管理器完成對其自身定義的這些結構的管理工作,以便今後使用。
圖11-6 呼叫LoadFSDList之後
圖11-7 與DSK連結之後
FSD_MountDisk首先呼叫GetDiskInfo得到設備的相關資訊,之後再由ReadWriteD isk讀入該設備上的第一個磁區—即啟動磁區的內容。由於啟動磁區中含有分區資訊表,因此可知道如何為該設備分配DSK和VOLUME等結構以保存這些資訊,並將它們管理起來。最後對各個卷以一級目錄的形式在文件系統驅動程式管理器中進行註冊。
註冊用到就是下面要分析的FSDMGR_RegisterVol ume函數,它負責申請並初始化一個VOL結構,然後呼叫RegisterAFSEx以及RegisterAFSName按照文件系統驅動程式指定的名稱對該卷進行註冊。註冊成功之後,文件系統驅動程式管理器就可以以目錄的形式將這些卷顯示出來了,並且使得應用程式可以藉由標準的文件系統A P I 函數對這些卷上的文件進行存取。
FSDMGR_RegisterVol ume函數的原型如下:
PVOL
FSD MGR_RegisterVOLUME (
PDSK pDSK ,
// 文件系統卷所在設備的_DSK結構指標
PWSTR pwsName,
// 希望把這個卷Mount到以此字符串為名的一級目錄中
PVOLUME pVOLUME
// 具體的FSD中用於管理卷使用的資料結構指標
// 對於FAT FSD而言它就是_VOLUME結構的指標
)
前兩個屬性沒什麼好解釋的,有疑問的是這裡的第三個參數。事實上,作為文件系統驅動程式管理器,它並不需要知道關於每一個卷的詳細資訊(比如說其中的分區從哪個磁區開始,又在哪個磁區結束),它的工作僅僅是去告訴相應的FSD,由它管理的哪一個卷需要進行什麼操作。
這些詳細的資訊對於文件系統驅動程式管理器是完全不必要的,也就不可能被放在相應的_VOL結構中。但對於具體的FSD,情況就不是這樣了。這裡的pVolume就指向了具體FSD中管理這個文件系統卷的資料結構(對於FAT FSD就是_VOLUME)。在FSDMGR_RegisterVolume中,所要做的就是簡單地把這個指標賦給(將被建立的)_VOL結構中的dwVo l Data屬性。
具體而言,這些入口是怎麼被註冊的呢?簡單的想來,系統中應該維護一個列表,列表中記錄了這些入口。一旦用戶呼叫文件系統A P I,如CreateFile,必然會傳給系統一個路徑。系統先從路徑名中分析出一級目錄的名字,然後開始從列表中進行搜索,看這個一級目錄是否是已經被登記的一個週邊入口,如果是,就把操作權轉讓給FSD Manager,FSD Manager會進一步把操作權轉給設備對應的FSD模組。
雖然這些註冊入口的函數源碼並沒有公開,但是不難通過呼叫函數的原型大概猜測出其註冊的方法。上面講的那種入口在CE中稱為A FS。一個AFS包括了這個入口的名稱,還記錄了是否有設備被Mount到了這個入口下,或者說是否有文件系統卷佔用了這個入口。此外還會有一個序號(或者說索引)同每一個AFS一一對應。系統中預設進行預先登記的那個Storage Card對應的AFS
序號是OID_FIRST_AFS。
對AFS進行具體操作的函數有如下三個:
1) int GetAFSName(int iAFS, PWSTR pwsAFS, int cchMax) 看i AFS這個索引所指向的AFS是否已經被一個文件系統卷佔用, p w s AFS會記錄下這個入口的名字。如果返回值為零,就說明這個入口尚未被佔用。
2) RegisterAFSName 建立一個AFS,其入口的名字就是此函數惟一的參數。返回值就是新建AFS一一對應的索引。
3) RegisterAFSEx 佔用一個AFS入口。函數需要的參數包括這個被佔用的AFS的索引(首先
透過上面的RegisterAFSName來建立),以及佔用這個入口的文件系統卷對應的_VOL結構指標等。
當一個文件系統捲進行Mount尋找(建立)AFS入口的時候,系統首先會從註冊表中查找是否有已經設定好的對應這個設備的預設入口名稱A( FSDMGR_RegisterVol ume呼叫時那個pwsName指向了這個名稱A,如果註冊表中沒有預設的名字,那麼這個指標就是NULL)。然後會判斷Storage Card這個預設的入口是否已經被佔用,如果尚未被佔用,就優先把卷Mount在這個入口中。否則判斷是否提供了預設的入口( pwsName是否為NULL)。如果兩個方向都被否定了,就比較麻煩了,需要自己起一個名字作為入口。
FSDMGR_RegisterVolume首先會呼叫AllocVolume為這個文件系統卷建立相應的_VOL結構。接下來,藉由一定的策略為這個卷找出一個入口名,最後把這個文件系統卷註冊在這個入口AFS
中。具體的原始碼如下:
*** PRIVATE\WINCEOS\COREOS\STORAGE\FSDSERV\Fsdserv.cpp Line 336 ***
PVOL FSDMGR_RegisterVolume(PDSK pDsk, PWSTR pwsName, PVOLUME pVolume)
{
PVOL pVol;
BOOL bSuccess = FALSE;
EnterCriticalSection(&csFSD);
// 如果已經為設備註冊了文件系統卷,先藉由DeregisterVOLUME註銷掉,再重新註冊
if (pDsk->pVol) {
FSDMGR_Deregister VOLUME( PDSK - > pVOL ) ;
pDsk->pVol = NULL;
}
// 呼叫Al loc VOLUME建立(並初始化) _VOL結構
if (pVol = AllocVolume(pDsk, (DWORD)pVolume)) {
PFSD pFSD = pVol->pDsk->pFSD;
// 接下來的工作就是給這個新的卷註冊一個AFS入口,首先需要找到合適的
// 入口名稱
if (pVol->iAFS == INVALID_AFS) {
int cch;
int iAFS, iSuffix = 1;
WCHAR wsAFS[128];
// 首先藉由GetAFSName看Storage Card這個預設的入口是否已經被佔用
cch = GetAFSName(OID_FIRST_AFS, wsAFS, ARRAY_SIZE(wsAFS));
// 如果沒有給出可供使用的入口名( pwsName = NULL)而且Storage Card
// 已經被佔用
if (pwsName == NULL && cch) {
// 後面會為這種最差的情況起一個入口名
iSuffix = 2;
pwsName = wsAFS;
}
// 如果沒有給出可供使用的入口名(或者入口名就是Storage Card),而這個
// 預設的入口並沒有被佔用
if (((pwsName == NULL) || (wcscmp(pwsName, L"Storage Card")==0))
&& !cch){
// 那麼直接呼叫RegisterAFSEx來佔用這個入口就可以了
if (RegisterAFSEx(OID_FIRST_AFS, hAFSAPI, (DWORD)pVol,
AFS_VERSION,
( pDSK - > dwFlags >> 1 6 ) & ( AFS_ FLAG_ROOTFS |
AFS_FLAG_HIDEROM | AFS_FLAG_BOOTABLE))) {
// 入口的AFS索引就是OID_FIRST_AFS
pVol->iAFS = OID_FIRST_AFS;
// 把入口名儲存在_VOL結構中
wcscpy( pVol->wsVol, wsAFS);
bSuccess = TRUE;
}
}
if (!bSuccess) {
// 如果上面的做法都不成功的話,就給Storage Card(或者給出的那
// 個名字)後面加上數字序號1、2. . .,如Storage Card1 ,Storage Card2
// 等等
wcscpy(wsAFS, pwsName[0] == '\\'? pwsName+1 : pwsName);
cch = wcslen(wsAFS);
do {
// 不斷嘗試各個序號
if (iSuffix > 1) {
wsAFS[cch] = '0'+iSuffix;
wsAFS[cch+1] = 0;
}
// 看這個新的名字(末尾加了序號)對應的入口是否已經被佔用
iAFS = RegisterAFSName(wsAFS);
// 如果尚未被使用,就可以藉由RegisterAFSEx來佔用它,否則的
// 話繼續循環(序號+ +)
if (iAFS != INVALID_AFS && GetLastError() == 0) {
if (RegisterAFSEx(iAFS, hAFSAPI, (DWORD)pVol,
AFS_VERSION,
(pDsk->dwFlags >> 16) & (AFS_FLAG_ROOTFS |
AFS_FLAG_HIDEROM | AFS_FLAG_BOOTABLE))) {
// 把入口名儲存在_VOL結構中
wcscpy( pVol->wsVol, wsAFS);
// 把AFS的索引儲存在_VOL結構中
pVol->iAFS = iAFS;
break ;
}
}
} while (iSuffix++ < 9);
}
}
// 如果連序號都試過(Storage Card1到Storage Card9),卻都被佔用了
//(這種情況應該很少見,畢竟C E不可能連接那麼多設備)
if (pVol->iAFS == INVALID_AFS) {
// 註冊(Mount)失敗,釋放前面申請的資源
D e Al loc VOLUME ( pVOL ) ;
pVol = NULL;
} else {
pVol->hNotifyHandle = NotifyCreateVolume(pVol->wsVol);
}
}
LeaveCriticalSection(&csFSD);
return pVol;
}
AllocVolume這個函數的目的同前面的AllocFSD,AllocDisk非常類似。主要就是建立(並初始化)一個同文件系統卷相對應的_VOL結構。比起AllocFSD和AllocDisk來要簡單很多。首先會申請_VOL結構需要的記憶體空間,然後做簡單的屬性設置就可以了。具體的原始碼如下:
*** PRIVATE\WINCEOS\COREOS\STORAGE\FSDSERV\Fsdalloc.cpp Line 298 ***
PVOL AllocVolume(
PDSK pDsk,
// 文件設備卷所屬設備的_DSK結構指標
DWORD dwVolData
// 具體FSD中用於管理這個文件系統卷使用的數
// 據結構指標,它會被簡單地賦給_VOL結構中的
// dwVolData屬性
)
{
PVOL pVol;
if (pDsk == NULL)
return NULL;
// 首先,申請_VOL結構需要的記憶體空間
pVol = (PVOL)LocalAlloc(LPTR, sizeof(VOL));
// 申請成功的話,進行簡單的初始化工作
if (pVol) {
INITSIG(pVol, VOL_SIG);
// 設定SIGFIELD
I n i t List ( ( PDLINK ) &pVOL->dlHdlList);
// 初始化_HDL結構的串列頭
pDsk->pVol = pVol;
// 設定_DSK的_VOL指標
pVol->dwFlags = 0;
pVol->pDsk = pDsk;
// 設定_VOL的_DSK指標
pVol->iAFS = INVALID_AFS;
// 現在尚未被Mount,所以索引還沒有意義
pVol->dwVolData = dwVolData;
// 把dwVOLData賦給_VOL中的相應屬性
}
// 最後返回_VOL結構的指標
return pVol;
}
所有這些工作結束之後,Ramdisk就會以一級目錄的形式展現出來,而這之後就可以藉由CreateFile、ReadFile和WriteFile這些標準的文件系統Win32 API介面對Ramdisk進行文件操作。圖11 - 8中的一級目錄「儲存卡」即為載入後的Ramdisk,這個一級目錄名稱正是圖11 - 4所示的註冊表中Folder的鍵值。當然,由於是中文版Windows CE的關係,Storage Card變成了儲存卡。
圖11-8 Ramdisk安裝成功之後以目錄的形式展現在用戶面前
11.2 FAT文件系統中CreateFile的具體流程:
11.2.1 實驗環境
所有的除錯工作均在Platform Builder 4.0下完成。目標機由Platform Builder自帶的模擬器代替。首先,建立一個BSP為Emulator的工程,選用設值為Internet Appliance。由於需要跟蹤的是FAT FSD的操作,所以還需要一個儲存週邊。由於Internet Appliance的設置並沒有提供相關週邊
的驅動,為了方便起見,採用了上一節介紹的\PUBLIC\COMMON\OAK\DRIVERS \BLOCK\RAMDISK下的虛擬磁盤驅動,從R A M中虛擬出一塊磁盤,並將其格式化為FAT。最後設備對應的路徑為「 \儲存卡\」
希望跟蹤的是FAT_CreateFileW這個函數,而這個函數對應最上層的系統呼叫是CreateFile,於是建立了一個應用程式,在程式中呼叫CreateFile。具體呼叫如下:
CreateFile ( L " \ \儲存卡\\MYFILE.TXT",
//文件名為MYFILE . TXT
GENERIC_READ,
//只讀打開
FILE_SHARE_READ ,
//允許讀共享
NULL ,
CREATE_NEW ,
//建立新的文件
FILE_ ATTRIBUTE_NORMAL,
NULL ) ;
11.2.2 參數介紹
1. PVOLUME pvol
一個指向_VOLUME的類型,標識FAT卷的資訊。程式運行時CallStack如下:
FATFSD ! FAT_CreateFileW
FSDMGR ! FSDMGR_CreateFileW
COREDLL ! xxx_AFS_CreateFileW
FILESYS ! FS_CreateFileW
NK ! SC_CreateFileW
COREDLL ! xxx_CreateFileW
當發生系統呼叫到最終執行FAT_CreateFileW,中間經過COREDLL,NK,FILESYS,FSDMGR這些程式的呼叫,而這個_VOLUME是由FSDMGR傳過來的,也就是說, FAT卷的相關資訊是從FSDMGR獲得的。
關於_VOLUME的結構定義如下:
struct _VOLUME {
HVOL
v_hVOL ;
//卷的相關資訊
VOL_DLINK
v_dlOpenVOLUMEs;
//磁盤上打開的卷的列表
S TM_DLINK
v_dlOpenStreams ;
//該捲上被打開的資料流的列表
#ifdef
PATH_CACHING
//如果對P A T H也進行CACHE
CCH_DLINK
v_dlCaches ;
//對應的CACHE列表
#Endif
PDSK
v_PDSK;
//該卷所在的磁盤的資訊
PPARTINFO
v_ppi ;
//分區資訊
DWORD
v_flags ;
//卷的標誌
signed char
v_VOLID ;
//文件系統標識,對於FAT是0 x 0 1
BYTE
v_cCaches ;
//當前Cache的入口
//
// 以下是需要在I n i t VOLUME時進行初始化的值
//
BYTE
v_bMediaDesc ;
//媒體的描述,從BPB中得到
BYTE
v_bVerifyCount ;
//需要做「寫後讀」檢查的數目
DWORD
v_serialID ;
//卷的序列號boot sector中的卷標
BYTE
v_label[OEMNAMESIZE+1] ;
// boot sector中的卷標
BYTE
v_log2cbSec ;
// log2(bytes per sector)
BYTE
v_log2cblkSec;
// log2(blocks per sector)
BYTE
v_log2csecClus ;
// log2(sectors per cluster)
BYTE
v_log2cblkClus ;
// log2(blocks per cluster)
DWORD
v_secVOLBias ;
//Sector的起始偏移值
DWORD
v_secB l kBias ;
//block 0 的偏移值
DWORD
v_blkClusBias ;
//cluster 0的偏移值
DWORD
v_cbClus ;
//每個cluster的字節數
DWORD
v_clusEOF ;
// cluster的EOF地址
DWORD
v_clusMax ;
// cluster數目的最大值
DWORD
v_clusAl loc ;
//被分配的最後一個cluster,沒有為- 1
DWORD
v_c clusFree ;
//尚未使用的cluster,沒有為- 1
DWORD
v_c secFAT ;
// FAT中Sector的數量
DWORD
v_secEndFAT ;
//當前活動FAT中結束的Sector
DWORD
v_secEndAl lFATs ;
//所有FAT結束的Sector
DWORD
v_c secUsed ;
// FAT和root使用的sectoer數
DWORD
v_c secTotal ;
//捲上的Sector數
#ifdef
FAT32 //FAT3 2
DWORD
v_secF S Info ;
//對應BIGFATBPB->BGBPB_ FSInfoSec
#Endif
UNPACKFN
* v_unpack ;
//FATunpack函數指標
PACKFN
* v_pack ;
//FATpack 函數指標
//
// 以上是需要在I n i t VOLUME時進行初始化的值
//
PDSTREAM
v_pstmFAT;
//讀寫FAT所用的資料流
PDSTREAM
v_pstmRoot ;
//讀寫根目錄所用的資料流
PWSTR
v_pwsHostRoot ;
//系統對應的目錄名稱,若沒有則為NULL
DWORD
v_cwsHostRoot ;
//上面目錄名稱的長度
CRITICAL_SECTION
v_cs;
//卷的臨界區
CRITICAL_SECTION
v_csStms;
// OpenStreams列表使用的臨界區
#ifdef PATH_CACHING
//如果對PATH也進行CACHE
CRITICAL_SECTION
v_csCaches;
// Cache列表使用的臨界區
# Endi f
#ifdef DISK_CACHING
LPBYTE
v_pFATCacheBuffer;
//磁盤的Cache Buffer
LPDWORD
v_pFATCacheLookup ;
//Lookup buffer for Cache
DWORD
v_CacheSize ;
// Cache的大小
# Endif
DWORD
v_f l FATFS;
//每一個FAT卷的標識
};
2. 磁區(Sector)和簇(Cluster)概念比較
磁區(Sector)是對週邊進行資料讀寫(或者說週邊儲存資料)的基本單位,每個磁區對應了一個惟一的實體扇號。藉由實體扇號,設備驅動程式就能夠讀取或者寫入資料。此外,每一個磁區相對於盤捲的引導磁區,還有邏輯扇號。從概念上講,邏輯扇號同實體扇號之間有一個隱藏磁區數的差別,但由於實際的單分區的設備,不存在隱藏磁區,所以兩者是等價的。
FAT中的文件區被統一劃分成了簇(Cluster)。從大小上看,每個簇對應的磁區個數必須是2的冪次方。從概念上講,簇是文件儲存的基本單位。每一個簇對應了FAT中的一個表項,它指出了這個簇的狀態(是否被佔用、是否是文件的最後一個簇、文件的下一個簇是什麼等)。把一個簇在FAT中對應的表項的索引( FAT中第一個表項的索引為0,以此類推),稱為這個簇的編號。由於FAT中的前兩個簇項不對應任何的簇,所以文件區中的第一個簇編號為2。
. HANDLE hProc 打開該文件的進程的識別碼,本函數中沒有使用。
. LPCWSTR lpFileName 文件的名稱在本例中為: \儲存卡\ MYFILE.TXT。
. DWORD dwAccess 文件的存取方式,GENERIC_READ,GENERIC_WR I TE或是完全存取。在本例中為:GENERIC_READ。
. DWORD dwShareMode允許共享的方式,FILE_SHARE_READ,FILE_SHA RE_WRITE或是完全共享。在本例中為: FILE_SHARE_READ。
. LPSECURITY_ATTRIBUTES lpSecurityAttributes 在此被忽略,無用參數。在本例中為:NULL。
. DWORD dwCreate 標識,當文件被建立或是被打開時的具體操作。在本例中為:CREAT E_NEW。
. DWORD dwFlagsAndAttributes 對應CreateFileW中的dwFlagsAndATTRIBUTE s。在本例中為:FILE_ ATTRIBUTE_NORMAL。
. HANDLE hTemplateFile 在此被忽略,無用參數。在本例中為:NULL。
11.2.3 局部變數
. BYTE mode 文件打開的模式,根據dwShareMode和dwAccess得到,在後面傳給CheckStreamSharing來檢查資料流的共享模式。
. int flName 文件屬性,根據dwCreat e和dwFlagsAndAttributes得到,在後面傳給OpenName標識文件名的類型(eg,NAME_FILE, NAME_DIR)和文件屬性(eg,ATTR_DIRECTORY )。
. HANDLE hFile 文件識別碼,由FSDMGR _ CreateFileHAndle建立,為FAT_CreateFileW的返回值。
. PFHANDLE pfh = NULL FHANDLE類型的指標。供FSDMGR_CreateFile HAndle使用。並將其加到pstm裡去。
. PDSTREAM pstm = NULL DSTREAM類型指標,對文件的具體操作均是對資料流的操作。
. DWORD dwError = ERROR_SUCCESS 出錯資訊,供除錯輸出時使用。
. BOOL bExists = FALSE 文件是否存在的標識。
11.2.4 過程跟蹤
當從一個系統呼叫CreateFile,到最終寫磁盤的操作,中間的過程可以用以下CAl l Stack表示:
FATFSD ! ReadWriteDisk 2
FATFSD ! ReadWriteDisk
FATFSD ! WriteVOLUME
FATFSD ! CommitBuffer
FATFSD ! CommitBufferSet
FATFSD ! CreateName
FATFSD ! OpenPath
FATFSD ! OpenName
FATFSD ! FAT_CreateFileW
FSDMGR ! FSDMGR_CreateFileW
COREDLL ! xxx_AFS_CreateFileW
FILESYS ! FS_CreateFileW
NK ! SC_CreateFileW
COREDLL ! xxx_CreateFileW
從FAT_CreateFileW開始以下的工作由FAT FSD來完成,從FAT_CreateFileW開始的呼叫到CreateName即是對資料流的操作,接著是對Buffer的操作,最後是I/O操作。
FAT_CreateFileW函數中呼叫的其他函數如下:
FAT_CreateFileW
FATEnter
OpenName
CheckStreamSharing
FSDMGR_ CreateFile HAndl e
FILESYSTEMNOTIFICATION
FAT
Exit
本次分析的重點是OpenName的實現以及往下的OpenPath等操作,至於後來的CommitBuffer和WriteVolume以及最終的ReadWriteDisk,限於篇幅只是簡要提到,如有興趣,可參考其原始碼。OpenName的呼叫實現了從Stream到BufferCache到最終的I/O操作,也貫穿了整個的FAT存取結構。
具體分析如下:
1) 呼叫FATEnter。
全域變數cFATThReads記錄了當前存取該FAT卷的執行緒的數量,所以在執行FATEnter時首先
要將cFATThReads加一。
2) 呼叫BufEnter。
全域變數cBufThReads記錄了當前使用BufferCache的執行緒數量,當要使用BufferCache的時候,將cBufThReads減一。
限制使用BufferCache的執行緒數的原因在於,如果同時使用的執行緒過多,很可能所有的Buffer都被佔用,那麼後來的執行緒就無法透過FindBuffer獲取自己需要的資料,執行緒的這次文件系統操作(用戶的系統呼叫)就會失敗。限制執行緒數目後,如果執行緒過多,即當cBufThReads < 0的時候阻塞,WaitForSingleObject(hevBufThreads, INFINITE),系統就讓它們等待h e v B u f T h Reads這個事件,執行緒的系統呼叫就不會失敗了。
3) 一系列的參數合法性判斷和一些局部變數的初始化和賦值。
if(pvol->v_flags & (VOLF_UNMOUNTED | VOLF_FROZEN | VOLF_WRITELOCKED))
//判斷當前卷是否可以被存取
{
dwError = ERROR_ACCESS_DENIED;
goto exit;
}
if (dwAccess & ~(GENERIC_READ | GENERIC_WRITE))
// dwAccess的合法性檢查
goto invalidParm;
if ((BYTE)dwFlagsAndAttributes == FILE_ATTRIBUTE_NORMAL)
dwFlagsAndAttributes &= ~0xFF;// 將dwFlagsAndATTRIBUTE s清零
else {
dwFlagsAndAttributes &= ~FILE_ATTRIBUTE_NORMAL;
if ((BYTE)dwFlagsAndAttributes & ~ATTR_CHANGEABLE)
goto invalidParm;}
dwFlagsAndAttributes |= FILE_ATTRIBUTE_ARCHIVE;
//在本次呼叫中, dwFlagsAndATTRIBUTE s最後的值為FILE _ ATTRIBUTE _ A R C H I V E
if (dwShareMode & ~(FILE_SHARE_READ | FILE_SHARE_WRITE))
// dwShareMode的合法性檢查
goto invalidParm;
flName = NAME_FILE | NAME_VOLUME;
switch (dwCreate) {
case CREATE_NEW:
flName |= NAME_NEW;
case CREATE_ALWAYS:
flName |= NAME_CREATE | NAME_TRUNCATE;
break ;
......
//以下省略,上面透過dwCreate得到flName值
mode = (BYTE)(ShareToMode(dwShareMode) | AccessToMode(dwAccess));
flName |= (BYTE)(dwFlagsAndAttributes) | (mode << NAME_MODE_SHIFT);
//得到mode和flName
4) 呼叫OpenName。
. 呼叫OpenPath。
PDSTREAM
// 返回的資料流指標
OpenPath (
PVOLUME
pvol,
// 文件系統卷指標
PCWSTR
pwsPath,
// 路徑名(若為/ A / B / C,返回B的資料流指標)
PCWSTR
* ppwsTail ,
int
* plen,
int
f l Name ,
// 打開屬性
DWORD
clusFail
)
{
int
len,
flTmp;
PCWSTR
pws,
pwsOrig;
DWORD
dwError = ERROR_SUCCESS;
PDSTREAM
pstm=NULL, pstmPrev = NULL ;
_try {
// 如果給出的路徑名空,直接退出
if (*pwsPath == 0)
goto exit;
//獲得_VOLUME中初始化時就打開的根目錄對應的資料流並把資料流的引用次數+ +)
pstm = OpenRoot(pvol);
if (!pstm) {
SetLastError (ERROR_ACCESS_DENIED ) ;
goto exit;
}
// 去掉路徑名中開頭的那個S l a s h
if (*pwsPath == TEXTW('\\') || *pwsPath == TEXTW('/'))
+ +pwsPath ;
pwsOrig = pwsPath;
// 開始循環,對於/ A / B / C,直到打開B的資料流為止
do {
pws = pwsPath;
// 找到下一個目錄的名稱(到Slash為止)
while (*pwsPath && *pwsPath != TEXTW('\\') && *pwsPath != TEXTW('/'))
++pwsPath;
//從兩個指標中可以看出這個目錄的長度
if (len = pwsPath - pws) {
if (len > MAX_LFN_LEN) {
dwError = ERROR_FILENAME_EXCED_RANGE;
break;
}
//如果這已經是最後一個元素了,那麼現在的pstm就指向了需要的資料流,退//出即可
if (*pwsPath == 0)
break;
//否則如果NAME_DIR屬性被設置,而路徑名稱類似於/ddd/jjj/rrr/,那麼,打開//j j
j的資料流就可以了,如果NAME_DIR沒有被設置,就需要打開rrr對應的//資料流才滿足要求
if (*(pwsPath+1) == 0 && (flName & NAME_DIR))
break ;
//設置呼叫OpenName的屬性為NAME_DIR
flTmp = NAME_DIR;
//呼叫OpenName來獲得這一層目錄對應的資料流
pstm = OpenName(pstmPrev = pstm, pws, len, &flTmp);
//上一層目錄對應的資料流不再需要了,關閉
CloseStream(pstmPrev);
//如果中間有一層目錄打開失敗,退出
if (!pstm)
break ;
}
//只要還有下一層目錄,就需要循環下去
} while (*pwsPath++);
}
_except (EXCEPTION_EXECUTE_HANDLER) {
dwError = ERROR_INVALID_PARAMETER;
}
//如果打開失敗,回傳錯誤
if (len == 0 && pstmPrev != NULL)
dwError = ERROR_PATH_NOT_FOUND;
//進行錯誤處理(關閉前面的資料流等等)
if (dwError) {
SetLastError(dwError);
if (pstm) {
CloseStream( pstm ) ;
pstm = NULL;
}
}
*ppwsTail = pws;
*plen = len;
e x i t :
//回傳資料流的指標
return pstm;
}
.具體的OpenName原始碼如下:
PDSTREAM
OpenName(
DSTREAM
pstmDir,
//當cwName非零的時候,這是檔案所屬的目錄檔
//案的資料流,否則就是所屬檔案文件的指標
PCWSTR
pwsName,
//當cwName非零的時候,這是純粹的檔案名,
//並不包括所在路徑,否則就是完整的路徑
int
cwName,
//零,或者是檔案名的長度
int *
pflName
//打開的屬性(不存在的時候是否該建立)
)
{
SHANDLE sh;
DIRINFO di;
WIN32_FIND_DATAW fd;
PDSTREAM pstmTmp = NULL;
PDSTREAM pstmRet = NULL;
int flName = *pflName;
DWORD dwError = ERROR_SUCCESS;
//如果cwName是零,就說明呼叫的時候,pstmDir沒有給出所在檔案夾的
//資料流,而是給出了所在檔案文件的指標。相對的, pwsName是完整
//的路徑名,因此需要透過OpenPath來打開檔案所屬的目錄檔案的資料流
if (cwName == 0) {
//呼叫OpenPath(假設傳遞的路徑是/A/B/C,就會回傳B的資料流指標)
pstmTmp = OpenPath((PVOLUME)pstmDir, pwsName, &pwsName,
&cwName,*pflName,UNKNOWN_CLUSTER ) ;
//如果呼叫OpenPath失敗,錯誤退出
if (pstmTmp == NULL)
goto exit;
//現在pstmDir就是C的上層目錄檔案B的資料流指標了,接下來的任務就是在目錄
//檔案B中尋找C了(如果沒找到,就可能產生新的目錄項)
pstmDir = pstmTmp;
}
_try {
//接下來開始為呼叫FindNext做準備,設置_SHANDLE結構的各個屬性
sh.sh_pstm = pstmDir;
// 指向目錄檔案對應的資料流
sh.sh_pos = 0;
//搜尋的起始位置是開頭(0)
sh.sh_flags = SHF_BYNAME;
//搜尋的方法是透過檔案名稱
//如果flName指出檔案不存在就建立,就給sh_flags加上SHF_CREATE
//這樣,FindNext就知道DIRINFO中的資訊是一定要填入的
if (flName & NAME_CREATE)
sh.sh_flags |= SHF_CREATE;
//把檔案名拷貝到_SHANDLE中去
sh.sh_cwPattern = (WORD)cwName;
memcpy(sh.sh_awcPattern, pwsName, cwName*sizeof(WCHAR));
sh.sh_awcPattern[cwName] = 0;
}
_except (EXCEPTION_EXECUTE_HANDLER) {
dwError = ERROR_INVALID_PARAMETER;
goto exit;
}
//現在就可以呼叫FindNext函數了
dwError = FindNext(&sh, &di, &fd);
//如果FindNext失敗,表示目錄中不存在此檔案
if (dwError) {
//如果需要建立,需要呼叫CreateName在目錄檔案中建立新的目錄項
SetLastError(ERROR_SUCCESS) ;
if (flName & NAME_CREATE) {
if (dwError != ERROR_INVALID_NAME) {
//呼叫CreateName來建立新檔案,並把FindNext設置的DIRINFO給它
dwError = CreateName(pstmDir, sh.sh_awcPattern, &di, NULL, flName);
flName &= ~(NAME_NEW | NAME_MODE_WRITE);
if (!dwError) {
*pflName |= NAME_CREATED;
}
}
}
else {
dwError = (flName & NAME_DIR? ERROR_PATH_NOT_FOUND : ERROR_FILE_NOT_FOUND);
}
}
//如果FindNext找到了所需的檔案,接下來開始建立資料流
else {
if ((flName & NAME_CREATE) || (flName & NAME_TRUNCATE))
SetLastError ( ERROR_ALREADY_EXISTS ) ;
}
//把檔案的屬性和打開的參數比較。比如說,如果目錄項說明這是一個目錄
//但要打開的卻不是目錄,而是資料檔案文件,那就出錯退出。同樣,如果打開
//的是唯讀檔案,卻要求以可寫模式打開,同樣也就錯誤退出,等等
if (!dwError) {
if (flName & NAME_NEW) {
dwError = (di.di_pde->de_attr & ATTR_DIRECTORY) || (flName & NAME_DIR)?
ERROR_ALREADY_EXISTS : ERROR_FILE_EXISTS;
}
else if (!(di.di_pde->de_attr & ATTR_DIRECTORY) && (flName & NAME_DIR)) {
dwError = ERROR_PATH_NOT_FOUND;
}
else if ((di.di_pde->de_attr & ATTR_DIRECTORY) && !(flName & NAME_DIR)) {
dwError = ERROR_ACCESS_DENIED;
}
else if ((di.di_pde->de_attr&ATTR_READ_ONLY)&&(flName&NAME_MODE_WRITE)){
dwError = ERROR_ACCESS_DENIED;
}
//一切滿足要求
else {
//最後透過OpenStream來打開(建立)資料流,現在終於知道OpenStream
//需要的參數是從哪裡來的了
pstmRet = OpenStream(pstmDir->s_pvol,
di.di_clusEntry ,
& di.di_sid,
pstmDir, &di, OPENSTREAM_CREATE);
if (!pstmRet)
dwError = ERROR_OUTOFMEMORY;
}
ReleaseStreamBuffer(pstmDir, FALSE);
}
exit:
if (dwError)
SetLastError ( dwError ) ;
if (pstmTmp)
//最後關閉目錄檔案的資料流
CloseStream ( pstmTmp ) ;
//回傳建立的(目標檔案的)資料流
return pstmRet;
}
5) 根據回傳的PDSTREAM pstm對流的共享性檢查CheckStreamSharing。
BOOL
CheckStreamSharing (
PDSTREAM
pstm,
//被檢查的資料流
int
mode
//需要對檔案進行的操作模式
//讀:FH_MODE_READ
//寫:FH_MODE_WRITE
)
{
BYTE
bRequired = 0;
PFHANDLE
pfh, pfhEnd;
ASSERT ( pstm ) ;
ASSERT ( OWNCRITICALSECTION ( &pstm->s_cs ) ) ;
//如果需要進行讀的動作,那就需要所有的_FHANDLE都允許共享可讀
if (mode & FH_MODE_READ)
bRequired |= FH_MODE_SHARE_READ;
//如果需要進行寫的動作,那就需要所有的_FHANDLE都允許共享可寫
if (mode & FH_MODE_WRITE)
bRequired |= FH_MODE_SHARE_WRITE;
pfh = pstm->s_dlOpenHandles.pfhNext;
pfhEnd = (PFHANDLE)&pstm->s_dlOpenHandles;
//接下來對資料流的_FHANDLE中的每一個進行檢查
while (pfh != pfhEnd) {
//只要有一個不能滿足要求就出錯退出
if ((pfh->fh_mode & bRequired) != bRequired)
return FALSE;
pfh = pfh->fh_dlOpenHandles.pfhNext;
}
return TRUE;
}
6) 使用FSDMGR_CreateFileHandle建立最後回傳的檔案識別碼hFile。
7) 根據參數對pfh的一些運算子,並將其加到p s t m裡去。
8) 呼叫FILESYSTEMNOTIFICATION通知系統,檔案被建立。
9) 最後呼叫FATExit,BuffExit( )將cBufThreads加一,釋放資源,並設置事件hevBufThreads,將cFATThreads加一。