首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 組合語言

實戰 DeviceIoControl 系列之三:打造磁碟鏡像檔案完整指南

     編輯:組合語言

前言:從 DISKCOPY 到自製鏡像工具

早期 DOS 時代的 DISKCOPY 命令讓許多工程師印象深刻,現代各式「克隆」軟體更能對磁碟進行完整區塊級複製。若要自行開發磁碟鏡像檔案製作工具,Windows 平台上的 DeviceIoControl API 無疑是核心利器。本文將以軟碟鏡像製作為例,完整示範如何利用裝置 I/O 控制碼實現類似 DISKCOPY 的功能。

核心架構設計

整個鏡像製作流程可拆解為四大階段:開啟裝置 → 取得幾何參數 → 鎖定/卸載磁碟區 → 逐磁道讀寫資料。以下各函式均採用同步 I/O 模式,確保資料傳輸的順序性與完整性。

1. 開啟實體磁碟裝置

使用 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;
}

2. 取得磁碟幾何資訊

透過 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);
}

3. 磁碟區鎖定與卸載

在進行原始讀寫前,必須先鎖定邏輯磁碟區(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);
}

4. 以磁柱為單位進行讀寫

根據幾何資訊計算每磁柱位元組數(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);
}

5. 低階格式化磁軌(可選)

若目標磁碟需先格式化,可使用 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;
}

完整操作流程範例

  1. 呼叫 OpenDisk(TEXT("\\\\.\\A:")) 取得裝置控制代碼
  2. 呼叫 GetDiskGeometry 取得幾何參數
  3. 依序呼叫 LockVolume、DismountVolume 獨佔磁碟
  4. 分配緩衝區,迴圈呼叫 ReadTracks 將資料寫入鏡像檔
  5. 完成後呼叫 UnlockVolume 釋放鎖定,關閉控制代碼

關鍵注意事項

  • 權限需求:需以系統管理員身分執行,否則 CreateFile 會因存取被拒而失敗
  • 媒體類型:DISK_GEOMETRY.MediaType 決定格式化參數,勿硬編碼假設為 1.44MB 軟碟
  • 錯誤處理:每個 DeviceIoControl 呼叫皆需檢查回傳值,失敗時立即呼叫 GetLastError 診斷
  • 資料完整性:建議讀取後計算 CRC32 或 SHA-256 校驗和,寫入前再次驗證

延伸應用

此架構同樣適用於硬碟分區備份、USB 隨身碟鏡像製作、虛擬機器磁碟轉換等場景。只需更改裝置路徑(如 \\.\PhysicalDrive0、\\.\C:)並調整緩衝區大小即可。結合 IOCTL_DISK_GET_LENGTH_INFO 可支援超過 2TB 的大容量磁碟。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved