请求和授予 oplock

当网络重定向程序访问远程服务器上的文件时,它会从远程服务器请求 oplock。 仅当锁适用于本地服务器上的文件时,客户端应用程序才直接请求 oplock。

Oplock 通过 FSCTL 请求。 以下 FSCTL 用于不同的 oplock 类型,用户模式应用程序和内核模式驱动程序都可以发出这些请求。

在用户模式下请求 Oplock

若要在用户模式下请求 Windows 7 oplock,请调用 DeviceIoControl

有关详细信息,请参阅 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_BUFFERREQUEST_OPLOCK_OUTPUT_BUFFER中较大者。
    • 将分配的 FLT_CALLBACK_DATAIopb->Parameters.FileSystemControl.Buffered.SystemBuffer 成员设置为指向该缓冲区。
    • 将分配的 FLT_CALLBACK_DATAIopb->Parameters.FileSystemControl.Buffered.InputBufferLengthIopb->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

关于如何设置 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 过程,应执行以下操作:

  1. 根据需要使用 FltCreateFileEx2ZwCreateFile打开文件。 在 CreateOptions 参数中传递标志 FILE_OPEN_REQUIRING_OPLOCK。 可以根据需要设置 DesiredAccessShareAccess 参数。 例如,在 DesiredAccess 参数中设置 GENERIC_READ 来读取文件,并在 ShareAccess 参数中设置 FILE_SHARE_READ | FILE_SHARE_DELETE 标志,以允许其他人在文件打开时读取、重命名和/或标记文件进行删除。
  2. 使用 FSCTL_REQUEST_OPLOCK 控制代码在生成的文件对象或句柄上请求 oplock,如上文在内核模式下请求 Oplock 中所述。

注意

不应对步骤 1 和 2 之间的文件执行任何文件系统操作。 这样做可能会导致死锁。

使用此过程请求的最常见 oplock 是 Read-Handle 类型。 这让你可以允许其他调用方实现尽可能多的并发访问,同时仍能在需要关闭句柄以避免共享违规而导致冲突时收到通知。

使用旧版筛选器 Oplock

旧版筛选器 oplock 还允许应用程序在其他应用程序/客户端尝试访问同一流时“退出”,但不如 atomic create-with-oplock 方法灵活。 此机制允许应用程序访问流,而不会导致其他访问流的程序在尝试打开流时发生共享冲突。 为了避免共享冲突,应使用以下三个步骤来请求筛选器 oplock:

  1. 以所需的 FILE_READ_ATTRIBUTES 访问权限和 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE 共享模式打开文件。 此步骤中打开的句柄不会导致其他应用程序收到共享冲突,因为它只用于属性访问(FILE_READ_ATTRIBUTES),而不是用于数据访问(FILE_READ_DATA)。 此句柄适用于请求筛选器 oplock,但不适用于对数据流执行实际 I/O。

  2. 从步骤 1 开始,在句柄上请求筛选器 oplock (FSCTL_REQUEST_FILTER_OPLOCK)。 此步骤中授予的 oplock 允许 oplock 持有者“退出”,而不会对试图访问流的另一个应用程序造成共享冲突。

  3. 再次打开该文件进行读取访问。 此步骤中打开的句柄允许 oplock 持有者在流上执行 I/O。

NTFS 文件系统通过 FILE_RESERVE_OPFILTER 创建选项标志为此过程提供优化。 如果在上一个过程的步骤 1 中指定了此标志,并且文件系统确定步骤 2 将会失败,则文件系统允许创建请求失败,并返回 STATUS_OPLOCK_NOT_GRANTED。 如果步骤 1 成功,则不能保证步骤 2 将成功,即使为创建请求指定了FILE_RESERVE_OPFILTER也是如此。

授予 Oplock 的条件

下表列出授予 oplock 所需的条件。

请求类型 条件

级别 1

滤波器

Batch

仅当以下所有条件均为真时授予:

  • 请求适用于给定的文件流。
    • 如果是目录,则返回STATUS_INVALID_PARAMETER。
  • 流已打开以供异步访问。
    • 如果为同步访问打开,则返回 STATUS_OPLOCK_NOT_GRANTED(不会为同步 I/O 请求授予 oplock)。
  • 文件的任何流上都没有 TxF 事务。
    • 否则,将返回 STATUS_OPLOCK_NOT_GRANTED。
  • 流上没有其他打开(即使由同一线程打开)。
    • 否则,将返回 STATUS_OPLOCK_NOT_GRANTED。

如果当前 oplock 状态为:

  • 无 oplock:请求已被授予。

  • 级别 2:原始级别 2 请求已被 FILE_OPLOCK_BROKEN_TO_NONE 中断。 然后授予请求的独占 oplock。

  • 级别 1、批处理、筛选器、读取、Read-Handle、Read-Write 或 Read-Write-Handle:返回 STATUS_OPLOCK_NOT_GRANTED。

级别 2

仅当以下所有条件均为真时才授予。

  • 请求适用于给定的文件流。
    • 如果是目录,则返回STATUS_INVALID_PARAMETER。
  • 流已打开以供异步访问。
    • 如果为同步访问打开,则返回 STATUS_OPLOCK_NOT_GRANTED。
  • 文件中没有 TxF 事务。
    • 否则,将返回 STATUS_OPLOCK_NOT_GRANTED。
  • 流中没有当前的字节范围锁。
    • 否则,将返回 STATUS_OPLOCK_NOT_GRANTED。
    • 在 Windows 7 之前,操作系统会验证自上次打开流以来,流上是否曾存在字节范围锁,如存在,则请求失败。

如果当前 oplock 状态为:

  • 无 oplock:请求已被授予。

  • 级别 2 和/或读取权限:请求已被授予。 可以同时在同一流上授予多个级别 2/读取 oplock。 同一句柄上甚至可以存在多个级别 2(但不是读取)oplock。
    • 如果在已获得读取 oplock 的句柄上请求读取 oplock,那么在第二个读取 oplock 被授予之前,第一个读取 oplock 的 IRP 将用 STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE 完成。
  • 级别 1、批处理、筛选器、Read-Handle、Read-Write、Read-Write-Handle:返回 STATUS_OPLOCK_NOT_GRANTED。

阅读

仅当以下所有条件均成立时授予:

  • 请求适用于给定的文件流。
  • 流已打开以供异步访问。
    • 如果为同步访问打开,则返回 STATUS_OPLOCK_NOT_GRANTED。
  • 文件中没有 TxF 事务。
    • 否则,将返回 STATUS_OPLOCK_NOT_GRANTED。
  • 流中没有当前的字节范围锁。
    • 否则,将返回 STATUS_OPLOCK_NOT_GRANTED。
  • 流中没有可写的用户映射的节
    • 否则,会返回 STATUS_CANNOT_GRANT_REQUESTED_OPLOCK。 REQUEST_OPLOCK_OUTPUT_BUFFER.Flags 字段将设置 REQUEST_OPLOCK_OUTPUT_FLAG_WRITABLE_SECTION_PRESENT 标志。

如果当前 oplock 状态为:

  • 无 oplock:请求已被授予。

  • 级别 2 和/或读取权限:请求已被授予。 可以同时在同一流上授予多个级别 2/读取 oplock。
    • 此外,如果现有 oplock 与新请求具有相同的 oplock 密钥,则其 IRP 将使用 STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE 完成。
  • Read-Handle 和现有 oplock 的 oplock 密钥与新请求不同:请求被授予。 多个读取和 Read-Handle oplock 可以共存在同一流上(请参阅本表后面的注释)。
    • 否则(oplock 密钥相同)将返回 STATUS_OPLOCK_NOT_GRANTED。
  • 级别 1、批处理、筛选器、Read-Write、Read-Write-Handle:返回 STATUS_OPLOCK_NOT_GRANTED。

Read-Handle

仅当以下所有条件均为真时授予:

  • 请求适用于给定的文件流。
  • 流已打开以供异步访问。
    • 如果为同步访问打开,则返回 STATUS_OPLOCK_NOT_GRANTED。
  • 文件中没有 TxF 事务。
    • 否则,将返回 STATUS_OPLOCK_NOT_GRANTED。
  • 流中没有当前的字节范围锁。
    • 否则,将返回 STATUS_OPLOCK_NOT_GRANTED。
  • 流中没有可写的用户映射的节
    • 否则,会返回 STATUS_CANNOT_GRANT_REQUESTED_OPLOCK。 REQUEST_OPLOCK_OUTPUT_BUFFER.Flags 字段将设置 REQUEST_OPLOCK_OUTPUT_FLAG_WRITABLE_SECTION_PRESENT 标志。

如果当前 oplock 状态为:

  • 无 oplock:请求已被授予。

  • 读取:请求已被授予。
    • 如果现有 Read oplock 与新请求具有相同的 oplock 密钥,则其 IRP 将使用 STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE 完成。 结果是将 oplock 从读取升级到 Read-Handle。
    • 任何与新请求没有相同 oplock 密钥的现有读取 oplock 都保持不变。
  • 级别 2、级别 1、批处理、筛选器、Read-Write、Read-Write-Handle:返回 STATUS_OPLOCK_NOT_GRANTED。

读/写

仅当以下所有条件均为真时授予:

  • 请求适用于给定的文件流。
    • 如果是一个目录,则返回 STATUS_INVALID_PARAMETER。
  • 流已打开以供异步访问。
    • 如果为同步访问打开,则返回 STATUS_OPLOCK_NOT_GRANTED。
  • 文件中没有 TxF 事务。
    • 否则,将返回 STATUS_OPLOCK_NOT_GRANTED。
  • 如果流上还有其他打开(即使是由同一线程打开),则它们必须具有相同的 oplock 密钥。
    • 否则,将返回 STATUS_OPLOCK_NOT_GRANTED。
  • 流中没有可写的用户映射的节
    • 否则,会返回 STATUS_CANNOT_GRANT_REQUESTED_OPLOCK。 REQUEST_OPLOCK_OUTPUT_BUFFER.Flags 字段将设置 REQUEST_OPLOCK_OUTPUT_FLAG_WRITABLE_SECTION_PRESENT 标志。

如果当前 oplock 状态为:

  • 无 oplock:请求已被授予。

  • 读取或 Read-Write 和现有的 oplock 与请求具有相同的 oplock 密钥:现有 oplock 的 IRP 使用 STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE 完成,请求被授予。
    • 否则,将返回 STATUS_OPLOCK_NOT_GRANTED。
  • 级别 2、级别 1、批处理、筛选器、Read-Handle、Read-Write-Handle:返回 STATUS_OPLOCK_NOT_GRANTED。

Read-Write-Handle

仅当以下所有条件均为 true 时授予:

  • 请求适用于给定的文件流。
    • 如果是目录,则返回STATUS_INVALID_PARAMETER。
  • 流已打开以供异步访问。
    • 如果为同步访问打开,则返回 STATUS_OPLOCK_NOT_GRANTED。
  • 文件中没有 TxF 事务。
    • 否则,将返回 STATUS_OPLOCK_NOT_GRANTED。
  • 如果流上还有其他打开请求(即使是由同一线程打开),则它们必须具有相同的 oplock 密钥。
    • 否则,将返回 STATUS_OPLOCK_NOT_GRANTED。
  • 流中没有可写的用户映射的节
    • 否则,会返回 STATUS_CANNOT_GRANT_REQUESTED_OPLOCK。 REQUEST_OPLOCK_OUTPUT_BUFFER.Flags 字段将设置 REQUEST_OPLOCK_OUTPUT_FLAG_WRITABLE_SECTION_PRESENT 标志。

如果当前 oplock 状态为:

  • 无 oplock:请求已被授予。

  • 读取、Read-Handle、Read-Write 或 Read-Write-Handle 和现有的 oplock 密钥与请求具有相同的 oplock 密钥:现有 oplock 的 IRP 将以 STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE 完成,请求被授予。
    • 否则,将返回 STATUS_OPLOCK_NOT_GRANTED。
  • 级别 2、级别 1、批处理、筛选器:返回 STATUS_OPLOCK_NOT_GRANTED。

注意

读取和级别 2 oplock 可以共存在同一流中,读取和 Read-Handle oplock 可以共存,但级别 2 和 Read-Handle oplock 不能共存。