如何刷新存储卷的文件缓存,锁定并卸载它,以及弹出介质






4.78/5 (11投票s)
演示如何打开一个存储卷并对其进行一些有趣的操作。
引言
弹出介质是 CD/DVD 驱动器的常见任务。在这里,我们进行物理弹出。但 ZIP/JAZ 驱动器和读卡器(带有可移动介质的驱动器)也支持弹出,并且由于它们不是只读的,因此需要在弹出之前卸载其文件系统以防止数据丢失。
背景
弹出介质是通过 DeviceIoControl
调用 IOCTL_STORAGE_EJECT_MEDIA
执行的。但这有点鲁莽,即使驱动器上有打开的文件,它也会弹出!因此,最好在执行此操作之前先刷新、锁定和卸载。
示例
此示例是我命令行工具 EjectMedia 的简化版本。
它期望将要弹出的驱动器盘符作为参数。对于非 CD/DVD 驱动器,第一步是使用 FlushFileBuffers
API 调用刷新文件缓存。它需要具有写入访问权限的句柄。因此,为了刷新整个存储卷,我们必须使用写入访问权限打开它,对于受限用户,这将失败并显示 ERROR_ACCESS_DENIED
// "\\.\X:" -> to open the volume
char szVolumeAccessPath[] = "\\\\.\\X:";
szVolumeAccessPath[4] = DriveLetter;
HANDLE hVolWrite = CreateFile(szVolumeAccessPath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
if ( hVolWrite != INVALID_HANDLE_VALUE ) {
FlushFileBuffers(hVolWrite);
CloseHandle(hVolWrite);
}
然后我们尝试锁定和卸载。在这里,只读访问权限就足够了,即使是受限用户也可以获得。只有当此卷上没有打开文件的句柄时,锁定才会成功。即使锁定失败且存在打开的句柄,卸载也会成功。卸载后,卷上的所有打开的文件句柄仍然打开但无效,任何尝试将其用于读写操作都会导致 ERROR_NOT_READY
。因此,即使锁定失败,也可以选择卸载,这就是 'Force
' 布尔值的含义。
bool Force = false;
HANDLE hVolRead = CreateFile(szVolumeAccessPath,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
if ( hVolRead != INVALID_HANDLE_VALUE ) {
int Locked = DeviceIoControl(hVolRead, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &dwRet, NULL);
if ( Locked || Force ) {
res = DeviceIoControl(hVolRead, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &dwRet, NULL);
}
}
最后,我们执行弹出操作
res = DeviceIoControl(hVolRead, IOCTL_STORAGE_EJECT_MEDIA, NULL, 0, NULL, 0, &dwRet, NULL);
由于我们很友好,如果获得锁,我们会释放它
if ( Locked ) {
DeviceIoControl(hVolRead, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, 0, &dwRet, NULL);
}
顺便说一句:弹出的反义词是加载介质。这仅适用于 CD/DVD 驱动器
res = DeviceIoControl(hVolRead, IOCTL_STORAGE_LOAD_MEDIA, NULL, 0, NULL, 0, &dwRet, NULL);
讨论
为什么没有检查驱动器类型 DRIVE_CDROM 和 DRIVE_REMOVABLE?
Windows 在这里不关心驱动器类型。弹出请求会传递到驱动器的驱动程序,然后驱动程序将其传递到硬件。最后,硬件会给出答案。有一些 USB 硬盘驱动器支持弹出。我见过安全移除失败但弹出成功的情况。
什么是确切的卸载?
卸载意味着将文件系统与存储卷分离。这保证了文件系统不会处于不一致状态,这在由另一个驱动程序挂载它之前很重要,例如,当系统处于休眠状态并且您从可启动 CD 启动时。
这与删除驱动器盘符 (mountvol X: /D) 相同吗?
不,绝对不是! /D 并不代表卸载,它代表删除挂载点。仅此而已。没有刷新,没有卸载,没有句柄被关闭或无效。
我们如何重新挂载卷?
如果锁被释放,那么只需尝试打开卷上的一个文件。Windows 随后会自动挂载该卷。在执行卸载之前已打开的句柄将无法用于读/写,即使该卷再次被挂载,它们也会永久生成 ERROR_NOT_READY
。
为什么锁定有时需要一段时间?
似乎锁定也会刷新文件缓存,这可能需要一点时间。此外,应用程序可以注册自定义通知 (RegisterDeviceNotification
),然后 DBT_CUSTOMEVENT
附带 GUID_IO_VOLUME_LOCK
。在所有注册了有关该卷的自定义通知的应用程序处理完消息之前,FSCTL_LOCK_VOLUME
不会返回。
演示项目
演示项目是用 VS6 制作的。它需要来自 Microsoft Platform SDK 的一些头文件。不需要特殊的 LIB。
历史
- 2011 年 9 月 26 日 - 首次发布。
- 2011 年 10 月 25 日 - 更正:卸载后的错误是
ERROR_NOT_READY
,而不是ERROR_INVALID_HANDLE
,并且在演示项目中缺少CloseHandle
。