早期 DOS 時代的 DISKCOPY 命令讓許多工程師印象深刻,現代各式「克隆」軟體更能對磁碟進行完整區塊級複製。若要自行開發磁碟鏡像檔案製作工具,Windows 平台上的 DeviceIoControl API 無疑是核心利器。本文將以軟碟鏡像製作為例,完整示範如何利用裝置 I/O 控制碼實現類似 DISKCOPY 的功能。
整個鏡像製作流程可拆解為四大階段:開啟裝置 → 取得幾何參數 → 鎖定/卸載磁碟區 → 逐磁道讀寫資料。以下各函式均採用同步 I/O 模式,確保資料傳輸的順序性與完整性。
使用 CreateFile 以 \\.\A: 等裝置名稱取得控制代碼,需同時請求 GENERIC_READ | GENERIC_WRITE 權限,並啟用共享模式以避免衝突:
HANDLE OpenDisk(LPCTSTR filename)
{
HANDLE hDisk = ::CreateFile(filename,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
return hDisk;
}
透過 IOCTL_DISK_GET_DRIVE_GEOMETRY 取得 DISK_GEOMETRY 結構,包含磁柱數、磁頭數、每軌扇區數、每扇區位元組數等關鍵參數,這是後續計算讀寫位移的基礎:
BOOL GetDiskGeometry(HANDLE hDisk, PDISK_GEOMETRY lpGeometry)
{
DWORD dwOutBytes;
return ::DeviceIoControl(hDisk,
IOCTL_DISK_GET_DRIVE_GEOMETRY,
NULL, 0,
lpGeometry, sizeof(DISK_GEOMETRY),
&dwOutBytes, NULL);
}
在進行原始讀寫前,必須先鎖定邏輯磁碟區(FSCTL_LOCK_VOLUME),再卸載檔案系統(FSCTL_DISMOUNT_VOLUME),強制系統重新辨識媒體,防止快取資料導致鏡像不一致:
BOOL LockVolume(HANDLE hDisk)
{
DWORD dwOutBytes;
return ::DeviceIoControl(hDisk, FSCTL_LOCK_VOLUME,
NULL, 0, NULL, 0, &dwOutBytes, NULL);
}
BOOL DismountVolume(HANDLE hDisk)
{
DWORD dwOutBytes;
return ::DeviceIoControl(hDisk, FSCTL_DISMOUNT_VOLUME,
NULL, 0, NULL, 0, &dwOutBytes, NULL);
}
根據幾何資訊計算每磁柱位元組數(TracksPerCylinder × SectorsPerTrack × BytesPerSector),利用 SetFilePointer 定位後呼叫 ReadFile/WriteFile 進行大塊傳輸:
BOOL ReadTracks(HANDLE hDisk, PDISK_GEOMETRY lpGeometry,
LPVOID pBuf, DWORD dwStartCylinder, DWORD dwCylinderNumber)
{
DWORD VirtBufSize = lpGeometry->TracksPerCylinder *
lpGeometry->SectorsPerTrack * lpGeometry->BytesPerSector;
::SetFilePointer(hDisk, VirtBufSize * dwStartCylinder, NULL, FILE_BEGIN);
DWORD BytesRead;
return ::ReadFile(hDisk, pBuf, VirtBufSize * dwCylinderNumber,
&BytesRead, NULL);
}
若目標磁碟需先格式化,可使用 IOCTL_DISK_FORMAT_TRACKS 配合 FORMAT_PARAMETERS 結構,並接收壞軌資訊:
BOOL LowLevelFormatTracks(HANDLE hDisk, PDISK_GEOMETRY lpGeometry,
DWORD dwStartCylinder, DWORD dwCylinderNumber)
{
FORMAT_PARAMETERS fp = {0};
fp.MediaType = lpGeometry->MediaType;
fp.StartCylinderNumber = dwStartCylinder;
fp.EndCylinderNumber = dwStartCylinder + dwCylinderNumber - 1;
fp.StartHeadNumber = 0;
fp.EndHeadNumber = lpGeometry->TracksPerCylinder - 1;
DWORD dwBufSize = lpGeometry->TracksPerCylinder * sizeof(BAD_TRACK_NUMBER);
PBAD_TRACK_NUMBER lpBadTrack = (PBAD_TRACK_NUMBER)new BYTE[dwBufSize];
DWORD dwOutBytes;
BOOL bResult = ::DeviceIoControl(hDisk, IOCTL_DISK_FORMAT_TRACKS,
&fp, sizeof(fp), lpBadTrack, dwBufSize, &dwOutBytes, NULL);
delete[] lpBadTrack;
return bResult;
}
OpenDisk(TEXT("\\\\.\\A:")) 取得裝置控制代碼GetDiskGeometry 取得幾何參數LockVolume、DismountVolume 獨佔磁碟ReadTracks 將資料寫入鏡像檔UnlockVolume 釋放鎖定,關閉控制代碼CreateFile 會因存取被拒而失敗DISK_GEOMETRY.MediaType 決定格式化參數,勿硬編碼假設為 1.44MB 軟碟DeviceIoControl 呼叫皆需檢查回傳值,失敗時立即呼叫 GetLastError 診斷此架構同樣適用於硬碟分區備份、USB 隨身碟鏡像製作、虛擬機器磁碟轉換等場景。只需更改裝置路徑(如 \\.\PhysicalDrive0、\\.\C:)並調整緩衝區大小即可。結合 IOCTL_DISK_GET_LENGTH_INFO 可支援超過 2TB 的大容量磁碟。