《Windows內核安全與驅動編程》-第九章-磁盤的虛擬-day-3.md

磁盤的虛擬

9.5 FAT12/16 磁盤卷初始化

9.5.2 Ramdisk 對磁盤的初始化

​ 在上一節中說到,在 Ramdisk 驅動中的 EvtDriverDeviceAdd 類函數裏會調用 RamDiskFormatDisk 函數對所分配的用於做磁盤鏡像的內存空間進行初始化。上一小節介紹了磁盤卷結構,接下來學習如何初始化這個空間。

​ 首先看一下這個函數的本地變量聲名。

//一個指向磁盤啓動扇區的指針
PBOOT_SECTOR bootSector = (PBOOT_SECTOR) devExt->DiskImage;
//指向FAT第一個FAT表的指針
PUCHAR		firstFatSector;
//用於記錄有多少個根目錄日寇點
ULONG rootDirEntries;
//用於記錄每個簇由多少扇區組成
ULONG sectorsPerCluster;
//用於記錄FAT文件系統的類型,是FAT12 還是16
USHORT fatType;
//用於記錄在FAT表裏一共有多少個表項
USHORT fatEntries;
//用於記錄一個FAT表需要佔用多少個扇區來存儲
USHORT fatSectorCnt;
//用於指向第一個根目錄入口點
PDIR_ENTRY rootDir;
//用於確定這個函數是可以存取分頁內存的
PAGED_CODE();
//用於確定這個盤的引導扇區的大小確實是一個扇區大小
ASSERT(sizeof(BOOT_SECTOR)) == 512);
//用於確定我們操作的磁盤不是一個不可用的指針
ASSERT(devExt->DiskImage != NULL);
//清空磁盤鏡像
RtlZeroMemory(devExt->DiskImage,devExt->DiskRegInfo.DiskSize);

​ 接下來格式化磁盤設備的設備擴展裏的數據結構 DiskGeometry 。它保存着與磁盤物理結構相關的信息。這個數據結構是 WinDDK 所定義好的,幾乎適用於所有磁盤。

typedef struce _DISK_GEOMETRY{
    //有多少個柱面
    LARGE_INTEGER Cylinders;
    //磁盤介質的類型
    MEDIA_TYPE MediaType;
    //每個柱面有多少磁道,也就是有多少個盤片
    ULONG TracksPerCylinder;
    //每個磁道有多少扇區
    ULONG SectorsPerTrack;
    //每個扇區有多少字節
    ULONG BytePerSector;
}DISK_GEOMETRY,*PDISK_GEOMETRY;

​ 該結構在今後的許多場合都會被作爲磁盤的參數而訪問。下面來給這些數據結構初始化

//每個扇區有512個字節
devExt->DiskGeometry.BytePerSector = 512;
//每個磁道有21個扇區
devExt->DiskGeometry.SectorsPerTrack = 32;
//每個柱面有兩個磁道
...
//柱面數據由磁盤的總容量決定
devExt->DiskGeometry.Cylinders.QuadPart = devExt->DiskRegInfo.DiskSize/512/32/2;
//我們自定義的 RAMDISK_MEDIA_TYPE 磁盤類型
devExt->DiskGeometry.MediaType = RAMDISK_MEDIA_TYPE;

​ 繼續初始化一些與文件系統相關的參數。

// 根據用戶指定的值對用戶的根目錄項的數據進行初始化
rootDirEntries = devExt->DiskRegInfo.RootDirEntries;
// 每個簇有多少扇區的初始化
sectorsPecCluster = devExt->DiskRegInfo.SectorsPerCluster;

​ 在一開始,我們讓 bootSector 指針指向了磁盤鏡像的首地址,聯繫之前的磁盤結構。首地址應該爲該分區的 DBR。所以接下來應該瞭解具體的 DBR 結構。

// 跳轉指令,跳轉到 DBR 中的引導程序
UCHAR bsJump[3];
// 該卷的 OEM 名稱
CCHAR bsOemName[8];
//每個扇區有多少字節
USHORT bsBytesPerSec;
//每個簇有多少扇區
UCHAR bsSecPerClus;
//保留扇區數據,指的是第一個 FAT 表開始之前的扇區數,也包括 DBR 本身
USHORT bsResSector;
//這個卷有多少個 FAT 表
USHORT bsFATs;
//這個卷的根目錄入口點有幾個
USHORT bsRootEntrents;
//這個卷一共有多少個扇區,如果大於65535個扇區,則該字段爲0
USHORT bsSectors;
//這個卷的介質類型
UCHAR bsMedia;
//每個 FAT 表佔用多少個扇區
USHORT bsFATsecs;
//每個磁道有多少個扇區
USHORT bsSecPerTrack;
//有多少個磁頭
USHORT bsHeads;
//有多少個隱藏扇區
ULONG bsHiddenSecs;
//一個卷超過65535個扇區時,會使用這個字段來說明總扇區數
ULONG bsHugeSectors;
//驅動器編號
UCHAR bsDriverNumber;
//保留字段
UCHAR bsReserved1;
//磁盤擴展引導區標籤,Windows 要求這個標籤爲 0x28或者0x29
UCHAR bsBootSignature;
//磁盤卷ID
ULONG bsVolumeID;
//磁盤卷標
CCHAR bsLabel[11];
//磁盤上的文件系統類型
CCHAR bsFileSystemType[8];
//保留字段
CCHAR bsReserved2[448];
//DBR 結束簽名
UCHAR bsSig2[2];

​ 不得不說,DBR 的結構比之前接觸到的結構都要複雜很多。所以接下來,又要以差不多的形式初始化 DBR

….初始化賦值

​ 值得一提的是,計算這個磁盤 FAT 表所佔用的空間。FAT 表裏存儲的是一個將很多簇串聯起來的鏈表,所以 FAT 表的表項的數量就是磁盤上實際用來存儲的簇的數量。而這個簇的數量又是由磁盤總扇區數減去用來存儲其他數據的扇區數之後除以每個簇的扇區數得到的。下面看一下如何實際計算得到:

//總扇區數減去保留的扇區數,再減去根目錄入口點佔據的扇區數,再除以每簇佔據的扇區數得到簇的數量
//最後+2 因爲FAT表的前兩項是保留的

fatEntries = (bootSector->bsSector - bootSector->bsResSectors-boosSector->bsRootDirents/DIR_ENTRIES_PER_SECTOR)/boosSector->bsSecPerClus+2

….後續也是類似的對 FAT 結構進行初始化。相應的機械性操作我選擇了略讀。也是因爲 Win10已經使用了NTFS結構,不再使用FAT12/16 文件結構。大致瞭解一下框架即可。

9.6 驅動中的請求處理

9.6.1 驅動中的請求處理

​ 在前面的介紹中知道,WDF 驅動框架會將所有發往之前建立的磁盤設備的請求都排隊放入已經建立的隊列中,而在放入隊列後絕大多數都得到了合適的處理。但是我們這裏需要自己註冊讀/寫和 DeviceControl 請求的回調函數來處理它們。

​ 回調函數在收到請求之後,只能執行下面列舉的4種操作的一種,但是不能忽略這個請求。這四種操作分別爲:

  1. 重新排隊。 回調函數可以把這個請求放到另一個隊列裏去等待其他的處理函數去處理。
  2. 完成請求。回調函數可以對這個請求做自己的一些處理,並且在處理完畢之後完成它。
  3. 撤銷請求,回調函數可以要求撤銷這個請求。
  4. 轉發請求。回調函數可以把請求轉發給其他的設備。

​ 在Windows系統當中,設備之間是一種層疊的關係,在這個磁盤設備之上還會有文件系統設備,一般應用程序的訪問都應該是訪問文件系統設備,而文件系統設備會負責做文件系統方面的維護。比如對 FAT 表的維護、對文件的讀寫等,而這些操作最終都會轉換成對磁盤的讀寫發往磁盤設備。

9.6.2 讀/寫請求

​ 讀與寫,是差不多的一類請求。參數類型一樣。

VOID RamDiskEvtIoRead(
	IN WDFQUEUE Queue,
	IN WDFREQUEST Request,
	IN size_t Length
);
VOID RamDiskEvtIoWrite(
	IN WDFQUEUE Queue,
	IN WDFREQUEST Request,
	IN size_t Length
);

​ 對於一個磁盤設備來說,讀寫請求就是要讀寫磁盤上的某一段區域的內容,這個區域由起點和長度來決定。長度已經由回調函數的參數提供,而起點就要通過 WDF 驅動框架提供的各種函數在第二個參數——請求參數中獲取了。讀寫請求還有一個重要的參數就是緩衝區,它由系統提供,用來存放讀出來或者寫入的數據,這個參數也需要從請求參數中獲取。

​ 在真實的應用中,在磁盤之上的文件系統設備會根據FAT表等數據結構,將對文件的訪問轉換成對磁盤設備的訪問,而磁盤對於上層來說,就是一個起始位置爲0,總長度爲磁盤卷總大小的扁平尋址空間。任何文件系統轉化過來的訪問都應該在這個空間之內。

​ 下面看一下讀請求的具體處理過程。寫請求與讀請求類似,不再重複。

//從隊列的擴展中取到對應的磁盤設備的擴展
PDEVICE_EXTENSION devExt = QueueGetExtension(Queue)->DeviceExtension;
//用於保存各種函數返回值的狀態變量
NTSTATUS Status = STATUS_INVALID_PARAMETER;
//用於獲取請求參數的變量
WDF_REQUEST_PARAMETERS Parameters;
//用戶獲取讀請求起始地址的變量,這裏要注意的是,這是一個64位的數據
LARGE_INTEGER ByteOffset;
//這是一個用於獲取讀緩衝區的內存句柄
WDFMEMORY hMemory;
//初始化參數變量,爲之後從請求參數中獲取各種信息做準備
WDF_REQUEST_PARAMETERS_INIT(&Parameters);
//從請求參數中獲取信息
WdfRequestGetParameters(Request,&Parameters);
//將請求參數中讀的起始位置取出來
ByteOffset.QuadPart = Parameters.Parameters.Read.DeviceOffset;
//這裏是自己實現的一個參數檢查函數。由於讀取的範圍不能超過磁盤鏡像的大小
//並且必須是扇區對齊,所以這裏需要有一個參數檢查。
if(RamDiskChrckParameters(devExt,ByteOffset,Length))
{
    //從請求參數中獲取讀緩衝區的內存句柄
    Status = WdfRequestRetrieveOutputMemory(Request,&hMemory);
    if(NT_SUCCESS(Status)){
        //根據之前獲取的讀參數進行內存拷貝,填寫這個讀請求的緩衝區
        Status - WdfMemoryCopyFromBuffer(
        	hMemory,
            0,//緩衝區的內存偏移量
            devExt->DiskImage+ByteOffset.LowPart,//source
            Length
        );
    }
}
//結束這個讀請求,這裏要注意的是,需要將讀取的長度作爲返回的信息一併返回
WdfRequestCompleteWithInformation(Request,Status,(ULONG_PTR)Length);

9.6.3 DeviceControl 請求

​ 一個標準的磁盤卷設備,需要支持數量龐大的 DeviceControl 請求,但是對於本例來說支持一個簡單的請求就足以示範了。DeviceControl 請求是系統發過來的一堆問題,例如這個磁盤有多大,它能寫什麼數據之類的問題。處理函數只需要按需回答即可。函數原型爲

VOID RamDiskEvtIoDeviceControl(
	IN WDFQUEUE Queue,
	IN WDFREQUEST Request,
	IN size_t OutputBufferLength,		//輸出緩衝區長度
	IN size_t InputBufferLength,		//輸入緩衝區長度
	IN ULONG IoControlCode				//是什麼樣的請求
)

​ 下面只處理一些,不處理這個設備就無法啓動或者不能正常工作的請求。具體爲什麼是這幾個請求,是開發人員長期的經驗和相關文檔來的….

//初始化返回狀態爲非法的設備請求,這樣在其他無關緊要的或者不處理的
//請求到來時候,可以直接返回該狀態
NTSTATUS Status = STATUS_INVALID_DEVICE_REQUEST;
//用來存放返回的 DeviceControl 所要求的數據長度
ULONG_PTR  information = 0 ;
//中間變量
size_z buffersize;
//與讀寫請求相同,通過隊列的擴展來獲取設備的拓展
PDEVICE_EXTENSION devExt  = QueueGetExtension(Queue)->DeviceExtension;
//由於是Windows發過來的標準請求,所以我們對請求的長度很信任。
//這裏不需要處輸入緩衝區和輸出緩衝區的長度
UNREFERENCED_PARAMETER(OutputBufferLength);
UNREFERENCED_PARAMETER(InputBufferLength);

switch(IoControlCode){
//這裏是一個獲取當前分區信息的 DeviceIoControl請求,需要處理
case IOCTL_DISK_GET_PARTITION_INFO:{}
	//首先聲名一個緩衝區指針
	PPARTITION_INFORMATION outputBuffer;
	//由於這個請求所需要的信息大部分是從 DBR 中獲取的,所以需要一個
	//指向DBR的指針
	PBOOT_SECTOR bootSector = (PBOOT_SECTOR) devExt->DiskImage;
	//告訴上層這裏返回信息的長度
	information = sizeof(PARTITION_INFORMATION);
	通過框架函數來獲取這個請求所攜帶的輸出緩衝區
	Status = WdfRequestRetrieveOutputBuffer(
		Request,
		sizeof(PARTITION_INFORMATION),
		&outputBuffer,		//緩衝區
		&bufferSize			//緩衝區大小
	);
	//在獲取緩衝區成功的情況下,將DNR中的相關信息填入緩衝區
	if(NT_SUCCESS(Status)){
        ...
        
	}
}
break;
//獲取當前磁盤硬件信息
case IOCTL_DISK_GER_DRIVER_GEOMETRY:{
    ...
}
   
}

​ 這裏大概就是底層磁盤的處理結構了。通過豐富這些請求,可以返回到上層許多的信息。後面還有兩個小章節講述該 虛擬磁盤的編譯和安裝。但是目前感覺實用性不強,畢竟現在 XP還是很少了。當下目的是學到底層的一些處理流程,瞭解大致的處理框架即可。細節不再追究。

明日計劃

繼續學習驅動編程-磁盤過濾

搞畢業論文

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章