原文:https://docstore.mik.ua/orelly/networking_2ndEd/nfs/ch11_01.htm#nfs2-CHP-11-SECT-1
我们介绍了文件锁的概念和两个主要组件:RPC 锁守护进程和状态监视器。 本章将更深入地研究文件锁并将检查管理方面。
什么是文件锁?
文件锁是一种确保当您访问文件时(通常是通过软件应用程序),在您完成检查之前没有人可以更改文件的行为。 如果您想修改文件,则文件锁可确保在您完成修改之前没有其他人可以检查或修改文件。
最早的 Unix 版本除了创建锁文件外,没有办法锁定文件。 这个想法是两个或多个进程将或多或少地同时尝试通过 open() 系统调用的 O_EXCL 标志以独占模式创建锁文件。 操作系统会将成功返回给赢得比赛的进程,并且将“文件存在”错误返回给失败的进程。 这种方案的一个问题是它依赖于获胜进程在退出之前删除锁定文件。 如果该进程正在运行有缺陷的软件,则可能不会发生这种情况。 一些应用程序通过将获胜者的进程 ID 记录到锁定文件的内容中来缓解此问题。 发现它得到“文件存在”错误的进程然后可以读取锁定文件以查看拥有的进程是否仍在运行。
不过,锁文件可能很笨拙。 在 1980 年代,Unix 版本发布,操作系统内置了文件锁支持。 Unix的System V分支通过fcntl()系统调用提供文件锁,而BSD分支提供flock()系统调用。 在这两种情况下,当创建锁的进程死亡时,锁将被自动释放。
独占锁和共享锁
fcntl和flock都提供了一种选择,要么是一个独占锁,其中只有一个进程可以持有该锁,要么是共享锁,其中多个持有者可以同时存在,排除了独占锁的持有者。独占锁有时称为“单写入进程”锁,因为它的排他性质允许安全写入文件。共享锁有时被称为“多读进程”锁,因为它的共享特性允许对文件进行多次安全读取。
记录锁
fcntl 系统调用还具有字节范围记录锁的特性。 这意味着应用程序可以将文件分割成任意大小的段或它想要的记录,并通过指定文件偏移量和长度来锁定它们。 因此,如果每个记录锁的文件偏移量和长度不重叠,则可以在文件上同时拥有独占锁和共享锁。
强制锁与建议锁
fcntl 和flock 都提供建议锁。 建议锁是需要参与进程合作的锁。 假设进程 A 获得了文件的独占锁,并打算写入它。 假设进程 B 打开文件并打算写入它。 如果进程 B 未能获得锁,则没有什么可以阻止它发出write系统调用并破坏 A 正在写入的进程。 出于这个原因,建议锁有时称为非强制锁。
SystemV(因此Solaris)提供强制或强制锁作为选项。如果对文件设置了强制锁权限,则启用此选项。强制锁权限是SetGID执行位(02000八进制)的重载。如果设置了set group ID 执行位,并且未设置组执行位,则对文件的所有读写都将使用强制锁定。例如:
% chmod 2644 example
% ls -l example
-rw-r-lr-- 1 mre staff 9 Dec 28 10:52 example
这使得文件example可以被文件的所有者读写,并且可以被其他人读取。 ls 命令输出的第一个字段中 l 的出现告诉您强制锁已启用。 当然,您可以对文件的所有者、组和世界使用读或写权限的任意组合。
如果在文件上设置了强制锁权限,那么每个 write() 或 read() 系统调用都会导致以下隐式序列:
fcntl(...); /* lock the file at the range we are reading or writing
*/ read(...); /* or */ write(...);
fcntl(...); /* unlock the file at the range locked above */
如果进程已经通过显式 fcntl 调用获得了锁怎么办? 如果锁定的范围等于或包含进行读取或写入的范围,则不会执行任何隐式 fcntl 调用对。 如果显式锁定的范围与读取或写入的范围部分重叠,则隐式 fcntl 调用在范围的未锁定部分上完成。
强制锁似乎非常有用,但它容易受到拒绝服务攻击。 假设在文件上设置了强制锁权限。 一个名为 Mallet 的攻击者决定发出 fcntl 调用以获取整个文件的独占锁。 Bob 现在尝试读取文件并发现他的应用程序挂起。 强制锁的支持者可能会指出错误在于允许 Mallet 访问文件(如果 Mallet 无法打开文件,他就无法锁定它)。 相反的观点是,如果您要依靠权限来避免拒绝服务(对于关键应用程序来说,受限权限是一件好事),那么可以访问文件的用户集仅限于那些对避免拒绝服务有既得利益的用户。 在这种情况下,强制锁并不比建议锁有用。
Windows/NT 锁方案
到目前为止的讨论都是关于Unix锁范例的。Windows世界有一个不同的范例。Unix和Windows锁之间有两个主要区别:
-
第一个区别是 Windows 世界支持共享保留编程接口。 共享保留适用于整个文件,并在创建或打开文件时指定。 共享保留由一对模式组成。 第一个是访问模式,即应用程序访问文件的方式:读、写或读/写。 第二个是应用程序将拒绝其他应用程序的访问:none、read、write 或 read/write。 当应用程序尝试打开文件时,操作系统会检查该文件是否还有其他打开请求。 如果是这样,它首先将应用程序的访问模式与其他打开者的拒绝模式进行比较。 如果有匹配,则拒绝打开。 如果不是,则操作系统将应用程序的拒绝模式与其他打开者的访问模式进行比较。 同样,如果有匹配,则拒绝打开。
-
第二个区别是没有建议锁。 整个文件锁、字节范围锁和共享保留锁都是强制的或强制的。
**提示:**Windows 世界中的共享保留根本不与 Windows 字节范围或整个文件锁交互。
NFS 和文件锁
NFS(版本 2 和 3)协议不支持文件锁定,但 NFS 环境支持称为 NLM 的辅助协议,它最初代表“网络锁定管理器”。 当 NFS 客户端上的 NFS 文件系统收到锁定文件的请求时,它会生成 NLM 远程过程调用,而不是 NFS 远程过程调用。
NLM协议
NLM 协议由对 fcntl 参数和结果进行模式化的远程过程调用组成。 因为支持阻塞锁(进程阻塞等待与另一个持有者冲突的锁),NLM 协议具有回调的概念,从文件服务器到 NLM 客户端通知锁可用。 通过这种方式,NLM 客户端有时充当 RPC 服务器,以便接收来自锁定调用的延迟结果。
NLM 恢复
NFS 协议是无状态的,但因为文件锁定本质上是有状态的,所以 NLM 是有状态的。 这导致从故障中恢复的更复杂的方案。 需要考虑三种类型的恢复方案:
- Server crash
- Client crash
- 网络分区
Server crash
当 NLM 服务器崩溃时,持有锁的 NLM 客户端必须在服务器重启时重新建立锁。 NLM 协议通过让服务器上的状态监视器向每个持有锁的 NLM 客户端的状态监视器发送通知消息来处理此问题。 服务器重启后的初始时期称为宽限期。 在宽限期内,仅授予重新建立锁的请求。 因此,可以保证在宽限期内重新建立锁的客户端不会丢失他们的锁。
Client crash
当 NLM 客户端崩溃时,最好将它当时持有的所有锁从它所锁定的所有 NLM 服务器中删除。 NLM 协议通过让客户端上的状态监视器在客户端重启后向每个服务器的状态监视器发送消息来处理此问题。 客户端重启指示告诉服务器客户端不再需要它的锁。
当然,如果客户端崩溃并且再也没有恢复,客户端的锁将无限期地持续存在。 这样做不好有两个原因:
- 资源无限泄露。
- 最终,另一个客户端将希望在崩溃的客户端锁定的至少一个文件上获得冲突锁定。 因此,另一个客户被无限期推迟。
这是您需要处理的管理问题之一,我们将在本章后面介绍。
网络分区
假设 NLM 客户端持有锁,但它与 NLM 服务器之间的网络路由发生故障:网络分区。 此时,从服务器的角度来看,情况与客户端崩溃但再也没有回来的情况没有区别。 同样,这是您需要处理的情况。
强制锁和 NFS
NLM 仅支持建议性的整个文件和字节范围锁,并且在部署 NFS 版本 4 之前,这意味着 NFS 环境不能支持强制性的整个文件和字节范围锁。 原因可以追溯到强制性锁如何与建议性 fcntl 调用交互。
假设ID为1867的进程对设置了强制锁权限的本地文件的整个范围发出fcntl独占锁调用。此fcntl调用是一个建议性锁。现在进程尝试写入文件。操作系统可以告诉进程1867持有建议性锁,因此,它允许写入继续进行,而不是在写入期间代表进程1867尝试获取建议性锁。现在假设进程1867在另一个具有强制锁权限的文件上执行相同的序列,但该文件位于NFS文件系统上。进程1867对设置了强制锁权限的文件的整个范围发出fcntl独占锁调用。现在,进程1867尝试写入该文件。虽然NLM协议在其锁请求中有字段来唯一标识客户端上锁文件的进程,但NFS协议没有字段来标识正在进行写入或读取的进程。该文件已锁定,并且设置了强制锁权限,但NFS服务器无法知道发送写请求的进程是否与获得锁的进程相同。因此,NFS服务器无法代表NFS客户端锁定文件。因此,一些NFS服务器(包括Solaris服务器)拒绝对设置了强制锁权限的文件进行任何读写。
NFS 和 Windows 锁定语义
NLM协议支持字节范围锁定和共享保留。
虽然 Windows 字节范围锁是强制性的,但在 Unix 服务器上它将是建议性的。 令 Windows 软件开发人员沮丧的是,这意味着非 PC/NFS 客户端可能会踩到 PC/NFS 客户端,因为非 PC/NFS 客户端不会尝试获取锁。 这也意味着同时支持 NFS/NLM 和 SMB 的服务器可能无法正确处理 NFS 客户端对 SMB 客户端已建立强制锁的文件进行读取或写入的情况。
PC/NFS 客户端将通过向 NLM 服务器发出共享保留远程过程调用来模拟共享保留语义。 但是,大多数非 PC/NFS 客户端,甚至 Unix NLM 服务器上的本地进程都不会遵守 PC/NFS 客户端共享保留的拒绝语义。 模拟的另一个问题是 Windows 语义期望共享保留和独占文件创建是原子的。 共享保留和文件创建作为单独的操作进行,因此没有原子性,允许存在漏洞窗口,客户端可以成功进行其独占创建,但无法获得共享保留。