请求和授予 oplock
当网络重定向程序访问远程服务器上的文件时,它会从远程服务器请求 oplock。 仅当锁适用于本地服务器上的文件时,客户端应用程序才直接请求 oplock。
Oplock 通过 FSCTL 请求。 以下 FSCTL 用于不同的 oplock 类型,用户模式应用程序和内核模式驱动程序都可以发出这些请求。
- 若要请求 Windows 7 oplock,请执行以下操作:
- 若要请求旧版 oplock,请执行以下操作:
在用户模式下请求 Oplock
若要在用户模式下请求 Windows 7 oplock,请调用 DeviceIoControl:
- 将 dwIoControlCode 设置为 FSCTL_REQUEST_OPLOCK。
- 将指针传递到 lpInBuffer 参数中的 REQUEST_OPLOCK_INPUT_BUFFER 结构。
- 有关如何格式化 oplock 请求的信息,请参阅该结构体的文档。
- 将指针传递到 lpOutBuffer 参数中的 REQUEST_OPLOCK_OUTPUT_BUFFER 结构。
有关详细信息,请参阅 FSCTL_REQUEST_OPLOCK。
如果可以授予请求的 oplock,DeviceIoControl 返回 FALSE,GetLastError 返回 ERROR_IO_PENDING。 因此,永远不会为同步 I/O 授予 oplock。 在 oplock 被中断之前,重叠操作不会完成。 操作完成后,REQUEST_OPLOCK_OUTPUT_BUFFER 将包含有关 oplock 中断的信息。
如果无法授予 oplock,则文件系统将返回相应的错误代码。 最常见的返回错误代码是 ERROR_OPLOCK_NOT_GRANTED 和 ERROR_INVALID_PARAMETER。
在内核模式下请求 Oplock
若要在内核模式下请求 Windows 7 oplock,请执行以下操作:
文件系统微型筛选器
文件系统微型筛选器必须使用 FltAllocateCallbackData,并按照如下方式填充已分配的 FLT_CALLBACK_DATA:
- 将其 Iopb->MajorFunction 字段设置为 IRP_MJ_FILE_SYSETM_CONTROL。
- 将其 Iopb->MinorFunction 字段设置为 IRP_MN_USER_FS_REQUEST。
- 将其 Iopb->Parameters.FileSystemControl.Buffered.FsControlCode 成员设置为 FSCTL_REQUEST_OPLOCK。
- 分配一个缓冲区,其大小等于 REQUEST_OPLOCK_INPUT_BUFFER 和 REQUEST_OPLOCK_OUTPUT_BUFFER中较大者。
- 将分配的 FLT_CALLBACK_DATA 的 Iopb->Parameters.FileSystemControl.Buffered.SystemBuffer 成员设置为指向该缓冲区。
- 将分配的 FLT_CALLBACK_DATA 的 Iopb->Parameters.FileSystemControl.Buffered.InputBufferLength 和 Iopb->Parameters.FileSystemControl.Buffered.OutputBufferLength 字段设置为该缓冲区的大小。
关于如何设置 oplock 请求格式的信息,请参阅 REQUEST_OPLOCK_INPUT_BUFFER 结构的文档。
然后,文件系统微型筛选器必须调用 FltPerformAsynchronousIo,并将分配的 FLT_CALLBACK_DATA 作为 CallbackData 参数传递。
如果可以授予请求的 oplock,FltPerformAsynchronousIo 调用将返回 STATUS_PENDING。 因此,永远不会为同步 I/O 授予 oplock。 在操作锁断开之前,该操作不会完成。 操作完成后,REQUEST_OPLOCK_OUTPUT_BUFFER 将包含有关 oplock 中断的信息。
如果无法授予 oplock,则文件系统将返回相应的错误代码。 最常见的返回错误代码是 STATUS_OPLOCK_NOT_GRANTED 和 STATUS_INVALID_PARAMETER。
其他类型的驱动程序
其他类型的驱动程序可以调用 ZwFsControlFile:
- 将 FsControlCode 设置为 FSCTL_REQUEST_OPLOCK。
- 将指针传递给 InputBuffer 参数中的 REQUEST_OPLOCK_INPUT_BUFFER 结构,并将 InputBufferLength 参数设置为该缓冲区的大小。
- 将指针传递给 OutputBuffer 参数中的 REQUEST_OPLOCK_OUTPUT_BUFFER 结构,并将 OutputBufferLength 参数设置为该缓冲区的大小。
关于如何设置 oplock 请求格式的信息,请参阅 REQUEST_OPLOCK_INPUT_BUFFER 结构的文档。
如果可以授予请求的 oplock,ZwFsControlFile 调用将返回 STATUS_PENDING。 因此,永远不会为同步 I/O 授予 oplock。 在操作锁断开之前,该操作不会完成。 操作完成后,REQUEST_OPLOCK_OUTPUT_BUFFER 将包含有关 oplock 中断的信息。
如果无法授予 oplock,则文件系统将返回相应的错误代码。 最常见的返回错误代码是 STATUS_OPLOCK_NOT_GRANTED 和 STATUS_INVALID_PARAMETER。
在请求 Oplock 时避免共享冲突
使用 Atomic Create-With-Oplock 方法
Atomic create-with-oplock 不是一种 Oplock 类型,而是一种程序,允许打开操作避免在打开文件和接收 oplock 之间的时间跨度内导致共享模式违规。 使用旧版 oplock 时,这只能通过筛选器 oplock 进行,并且需要打开两个句柄。 使用 Windows 7 oplock 功能时,应用程序或驱动程序可以使用此过程请求任何类型的 oplock,并且只需打开一个句柄。
若要执行 atomic create-with-oplock 过程,应执行以下操作:
- 根据需要使用 FltCreateFileEx2 或 ZwCreateFile打开文件。 在 CreateOptions 参数中传递标志 FILE_OPEN_REQUIRING_OPLOCK。 可以根据需要设置 DesiredAccess 和 ShareAccess 参数。 例如,在 DesiredAccess 参数中设置 GENERIC_READ 来读取文件,并在 ShareAccess 参数中设置 FILE_SHARE_READ | FILE_SHARE_DELETE 标志,以允许其他人在文件打开时读取、重命名和/或标记文件进行删除。
- 使用 FSCTL_REQUEST_OPLOCK 控制代码在生成的文件对象或句柄上请求 oplock,如上文在内核模式下请求 Oplock 中所述。
注意
不应对步骤 1 和 2 之间的文件执行任何文件系统操作。 这样做可能会导致死锁。
使用此过程请求的最常见 oplock 是 Read-Handle 类型。 这让你可以允许其他调用方实现尽可能多的并发访问,同时仍能在需要关闭句柄以避免共享违规而导致冲突时收到通知。
使用旧版筛选器 Oplock
旧版筛选器 oplock 还允许应用程序在其他应用程序/客户端尝试访问同一流时“退出”,但不如 atomic create-with-oplock 方法灵活。 此机制允许应用程序访问流,而不会导致其他访问流的程序在尝试打开流时发生共享冲突。 为了避免共享冲突,应使用以下三个步骤来请求筛选器 oplock:
以所需的 FILE_READ_ATTRIBUTES 访问权限和 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE 共享模式打开文件。 此步骤中打开的句柄不会导致其他应用程序收到共享冲突,因为它只用于属性访问(FILE_READ_ATTRIBUTES),而不是用于数据访问(FILE_READ_DATA)。 此句柄适用于请求筛选器 oplock,但不适用于对数据流执行实际 I/O。
从步骤 1 开始,在句柄上请求筛选器 oplock (FSCTL_REQUEST_FILTER_OPLOCK)。 此步骤中授予的 oplock 允许 oplock 持有者“退出”,而不会对试图访问流的另一个应用程序造成共享冲突。
再次打开该文件进行读取访问。 此步骤中打开的句柄允许 oplock 持有者在流上执行 I/O。
NTFS 文件系统通过 FILE_RESERVE_OPFILTER 创建选项标志为此过程提供优化。 如果在上一个过程的步骤 1 中指定了此标志,并且文件系统确定步骤 2 将会失败,则文件系统允许创建请求失败,并返回 STATUS_OPLOCK_NOT_GRANTED。 如果步骤 1 成功,则不能保证步骤 2 将成功,即使为创建请求指定了FILE_RESERVE_OPFILTER也是如此。
授予 Oplock 的条件
下表列出授予 oplock 所需的条件。
请求类型 | 条件 |
---|---|
级别 1 滤波器 Batch |
仅当以下所有条件均为真时授予:
如果当前 oplock 状态为:
|
级别 2 |
仅当以下所有条件均为真时才授予。
如果当前 oplock 状态为:
|
阅读 |
仅当以下所有条件均成立时授予:
如果当前 oplock 状态为:
|
Read-Handle |
仅当以下所有条件均为真时授予:
如果当前 oplock 状态为:
|
读/写 |
仅当以下所有条件均为真时授予:
如果当前 oplock 状态为:
|
Read-Write-Handle |
仅当以下所有条件均为 true 时授予:
如果当前 oplock 状态为:
|
注意
读取和级别 2 oplock 可以共存在同一流中,读取和 Read-Handle oplock 可以共存,但级别 2 和 Read-Handle oplock 不能共存。