4

我的公司正在开发一个在 Windows 7 下运行的“花式”USB 大容量存储设备。处理客户端实际存储介质读取和写入的大容量存储客户端驱动程序是用 C++ 编写的。我们遇到的问题是写入速度非常非常慢。比预期慢大约 30 倍。我们正在使用对 WriteFile() 的调用将数据块写入存储介质(特别是物理驱动器“PhysicalDrive2”),因为它们是从主机设备接收到的。我在许多其他论坛上读到,人们使用 WriteFile() 的写入速度非常慢,尤其是在 Windows 7 上。所以我试图弄清楚我是否为这个特定任务使用了最好的方法和函数调用。

下面是两个代码块。一个用于 LockVolume() 函数,在初始化期间由程序调用一次,实际上只是卸载卷。另一个代码块是 WriteSector(),它用于在 USB 客户端控制器驱动程序接收到实际数据时将其写入物理驱动器。我希望有人可以阐明我可能做错了什么,或者就更好的实现方式提供建议。

    short WriteSector
       (LPCWSTR _dsk,    // disk to access
       char *&_buff,         // buffer containing data to be stored
       unsigned int _nsect,   // sector number, starting with 0
       ULONG    Blocks
       )
{
    DWORD bytesWritten;   
    wchar_t errMsg[256];

    //attempt to get a handle to the specified volume or physical drive
    HANDLE hDisk = CreateFile(_dsk, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

    //make sure we have a handle to the specified volume or physical drive
    if(hDisk==INVALID_HANDLE_VALUE) 
    {  
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errMsg, 255, NULL);    
        OutputDebugString(errMsg);
        printf("Error attempting to get a handle to the device! (%s)\n", errMsg);
        goto exit;
    }

    // set pointer to the sector on the disk that we want to write to
    SetFilePointer(hDisk, (_nsect * SIZE_OF_BLOCK), 0, FILE_BEGIN); 

    //write the data
    if (!WriteFile(hDisk, _buff, (Blocks * SIZE_OF_BLOCK), &bytesWritten, NULL))
    {
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
                        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errMsg, 255, NULL);  
        printf("WriteFile failed! (%s)\n", errMsg);
        goto exit;
    }

exit:
    CloseHandle(hDisk);

    writeMutex.unlock();

    return 0;
}

    UINT Disk_LockVolume(LPCWSTR _dsk)
{       
    HANDLE hVol;
    LPWSTR errMsg;
    DWORD status;
    bool success = false;

    //now try to get a handle to the specified volume so we can write to it
    hVol = CreateFile(_dsk, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

    //check to see if we were able to obtain a handle to the volume
    if( hVol == INVALID_HANDLE_VALUE )
    {
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errMsg, 255, NULL);    

        printf("Disk_LockVolume() - CreateFile failed (%s)\n", errMsg);
        goto exit;
    }

    // now lock volume
    if (!DeviceIoControl(hVol, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &status, NULL))
    {
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errMsg, 255, NULL);    
        printf("Disk_LockVolume() - Error attempting to lock device!  (%s)\n", errMsg);
        goto exit;
    }

    //dismount the device 
    if (!DeviceIoControl(hVol, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &status, NULL))
    {
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errMsg, 255, NULL);    
        printf("Disk_LockVolume() - Error attempting to dismount volume.  (%s)\n", errMsg);
        goto exit;
    }


exit:
    CloseHandle(hVol);

    return 1;
}

编辑#1(2015 年 2 月 10 日)

所以我结合了 Ben Voigt 提出的建议,发现只调用一次 CreateFile 和 CloseHandle(而不是每次我们想将数据写入驱动器)显着提高了写入速度。增加 80%。即使有了这样的增加,写入速度仍然比预期的要慢得多。慢了大约 6 倍。因此,我随后合并了他的其他建议更改,其中包括消除对 SetFilePointer() 的原始调用,并将其替换为现在传递给 WriteFile 的 OVERLAPPED 结构。进行更改后,我现在收到错误消息,指出“变量'MyOverLappedStructure'周围的堆栈已损坏”。下面是我的 SectorWrite 函数的更新版本以及获取物理驱动器初始句柄的新 Disk_GetHandle() 函数。还,在我调用 Disk_GetHandle() 之后,我仍在调用 Disk_LockVolume()。但是,我已经修改了 Disk_LockVolume() 函数,以便卷的句柄(在这种情况下)不会在函数结束时关闭。最终,这将在程序结束时关闭,然后再关闭物理驱动器上的句柄。对这个新错误的任何想法将不胜感激。哦,我可以看到 FILE_FLAG_NO_BUFFERING 对性能没有影响。

UINT WriteSector(HANDLE hWriteDisk, PBYTE   Buf, ULONG   Lba, ULONG Blocks)
{       
    DWORD bytesWritten;
    LPTSTR errMsg = "";

    //setup overlapped structure to tell WriteFile function where to write the data
    OVERLAPPED overlapped_structure;
    memset(&overlapped_structure, 0, (Blocks * SIZE_OF_BLOCK));
    overlapped_structure.Offset = (Lba * SIZE_OF_BLOCK);
    overlapped_structure.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);


    //write the data
    if (!WriteFile(hWriteDisk, Buf, (Blocks * SIZE_OF_BLOCK), &bytesWritten, &overlapped_structure))
    {
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errMsg, 255, NULL);    
        printf("WriteSector() - WriteFile failed (%s)\n", errMsg);
    }

    if (bytesWritten != (Blocks * SIZE_OF_BLOCK))
    {
        printf("WriteSector() - Bytes written did not equal the number of bytes to be written\n");
        return 0;
    }
    else
    {
        return Blocks;
    }
}

HANDLE Disk_GetHandle(UINT Lun)
{       
    HANDLE hVol;
    LPTSTR errMsg = "";
    bool success = false;

    //now try to get a handle to the specified volume so we can write to it
    hVol = CreateFile(MassStorageDisk[Lun].PhysicalDisk, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED|FILE_FLAG_NO_BUFFERING, 0);

    //check to see if we were able to obtain a handle to the volume
    if( hVol == INVALID_HANDLE_VALUE )
    {
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errMsg, 255, NULL);
        printf("Disk_WriteData() - CreateFile failed (%s)\n", errMsg);
    }

    return hVol;
}

编辑#2(2015 年 2 月 10 日)

因此,根据 Ben 的评论,我从 CreateFile() 调用中消除了 FILE_FLAG_OVERLAPPED 。我还修改了 WriteSector() 函数的一部分,以包括在调用 WriteFile() 后检查 IO 是否处于挂起状态。如果是这样,我调用 WaitForSingleObject() 无限期地等待直到 IO 操作完成。最后,我在 OVERLAPPED 结构 hEvent 上调用 CloseHandle()。即使进行了这些更改,我仍然收到错误“围绕变量 'osWrite' 的堆栈已损坏”,其中 osWrite 是 OVERLAPPED 结构。以下是说明更改的代码片段。

OVERLAPPED osWrite;
memset(&osWrite, 0, (Blocks * SIZE_OF_BLOCK));
osWrite.Offset = (Lba * SIZE_OF_BLOCK);
osWrite.hEvent = 0;


//write the data
if (!WriteFile(hWriteDisk, Buf, (Blocks * SIZE_OF_BLOCK), &bytesWritten, &osWrite))
{
    DWORD Errorcode = GetLastError();
    if (Errorcode == ERROR_IO_PENDING)
    {
        WaitForSingleObject(osWrite.hEvent, INFINITE);
    }
    else
    {
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errMsg, 255, NULL);    
        printf("WriteSector() - WriteFile failed (%s)\n", errMsg);
        goto exit;
    }
}

编辑#3(2015 年 2 月 10 日)

所以代码现在正在处理 Ben 的输入。上面的代码已被修改以反映这些更改。我需要提一下,直到今天下午,我的所有测试都是在客户端的存储介质是 USB 闪存驱动器的情况下完成的。我已经改变了它,所以客户端现在写入连接的 SSD。使用 USB 闪存驱动器设置后,我通过 USB 连接向客户端写入数据的速度现在几乎与客户端 SBC 可以将相同文件从自身传输到存储介质的速度(没有主机连接的)。但是,现在使用 SSD,情况并非如此。我使用的 34MB 测试文件从客户端 SBC 直接传输到 SSD 需要 2.5 秒。通过 USB 从主机到客户端需要 2.5 分钟。

4

1 回答 1

5

You should not call CreateFile and CloseHandle for each sector overwritten. CreateFile is a very expensive operation that has to do security checks (evaluate group membership, walk SIDs, etc).

Open the handle once, pass it to WriteFile many times, and close it once. This means changing your _dsk parameter from a volume path to a HANDLE.

You probably also want to lose the call to SetFilePointer, and use an OVERLAPPED structure instead, which lets you supply the position to write to as part of the write call. (The operation won't be overlapped unless you use FILE_FLAG_OVERLAPPED, but non-overlapped I/O respects the position information in the OVERLAPPED structure).

于 2015-02-06T23:24:04.217 回答