基本概念
Linux中软件、硬件资源都是文件(一切皆文件),文件在多用户环境中是可共享的。文件锁是用于解决资源的共享使用的一种机制:当多个用户需要共享一个文件时,Linux通常采用的方法是给文件上锁,来避免共享的资源产生竞争的状态。
在多任务操作系统环境中,如果一个进程尝试对正在被其他进程读取的文件进行写操作,可能会导致正在进行读操作的进程读取到一些被破坏或者不完整的数据;如果两个进程并发对同一个文件进行写操作,可能会导致该文件遭到破坏。因此,为了避免发生这种问题,必须要采用某种机制来解决多个进程并发访问同一个文件时所面临的同步问题,由此而产生了文件加锁方面的技术。 早期的 UNIX 系统只支持对整个文件进行加锁,因此无法运行数据库之类的程序,因为此类程序需要实现记录级的加锁。在 System V Release 3 中,通过 fcntl 提供了记录级的加锁,此后发展成为 POSIX 标准的一部分。本文将基于 2.6.23 版本的内核来探讨 Linux 中文件锁的相关技术。文件锁类型
Linux 支持的文件锁技术主要包括劝告锁(advisory lock)和强制锁(mandatory lock)这两种。
- 劝告锁 劝告锁是一种协同工作的锁。对于这一种锁来说,内核只提供加锁以及检测文件是否已经加锁的手段,但是内核并不参与锁的控制和协调。也就是说,如果有进程不遵守“游戏规则”,不检查目标文件是否已经由别的进程加了锁就往其中写入数据,那么内核是不会加以阻拦的。因此,劝告锁并不能阻止进程对文件的访问,而只能依靠各个进程在访问文件之前检查该文件是否已经被其他进程加锁来实现并发控制。进程需要事先对锁的状态做一个约定,并根据锁的当前状态和相互关系来确定其他进程是否能对文件执行指定的操作。从这点上来说,劝告锁的工作方式与使用信号量保护临界区的方式非常类似。
劝告锁可以对文件的任意一个部分进行加锁,也可以对整个文件进行加锁,甚至可以对文件将来增大的部分也进行加锁。由于进程可以选择对文件的某个部分进行加锁,所以一个进程可以获得关于某个文件不同部分的多个锁。
2. 强制锁 与劝告锁不同,强制锁是一种内核强制采用的文件锁,它是从 System V Release 3 开始引入的。每当有系统调用 open()、read() 以及write() 发生的时候,内核都要检查并确保这些系统调用不会违反在所访问文件上加的强制锁约束。也就是说,如果有进程不遵守游戏规则,硬要往加了锁的文件中写入内容,内核就会加以阻拦: 如果一个文件已经被加上了读锁或者共享锁,那么其他进程再对这个文件进行写操作就会被内核阻止; 如果一个文件已经被加上了写锁或者排他锁,那么其他进程再对这个文件进行读取或者写操作就会被内核阻止。 如果其他进程试图访问一个已经加有强制锁的文件,进程行为取决于所执行的操作模式和文件锁的类型,归纳如表 2 所示:当前锁类型 | 阻塞读 | 阻塞写 | 非阻塞读 | 非阻塞写 |
---|---|---|---|---|
读锁 | 正常读取数据 | 阻塞 | 正常读取数据 | EAGAIN |
写锁 | 阻塞 | 阻塞 | EAGAIN | EAGAIN |
Linux中关于文件锁的系统调用
这里介绍在 Linux 中与文件锁关系密切的两个系统调用:flock()和fcntl()。劝告锁既可以通过系统调用 flock() 来实现,也可以通过系统调用fcntl()来实现。flock()系统调用是从 BSD 中衍生出来的,在传统的类UNIX操作系统中,系统调用flock()只适用于劝告锁.flock()只能实现对整个文件进行加锁,而不能实现记录级的加锁。系统调用fcntl() 符合 POSIX 标准的文件锁实现,它也是非常强大的文件锁,fcntl() 可以实现对纪录进行加锁锁。
flock()
函数原型:
int flock(int fd, int operation);
参数: - fd:表示文件描述符
- LOCK_SH:表示要创建一个共享锁,在任意时间内,一个文件的共享锁可以被多个进程拥有
- LOCK_EX:表示创建一个排他锁,在任意时间内,一个文件的排他锁只能被一个进程拥有
- LOCK_UN:表示删除该进程创建的锁
- LOCK_NB : 非阻塞
lockf()
函数原型 int lockf(int fd, int cmd, off_t len);
- fd:文件描述符号
- F_LOCK:给文件互斥加锁,若文件以被加锁,则会一直阻塞到锁被释放
- F_TLOCK:同F_LOCK,但若文件已被加锁,不会阻塞,而回返回错误
- F_ULOCK:解锁
- F_TEST:测试文件是否被上锁,若文件没被上锁则返回0,否则返回-1
- len:为从文件当前位置的起始要锁住的长度
fcntl()
函数原型
int fcntl(int fd, int cmd, ... /* arg */ ) ;
struct flock { ... short l_type; /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */ short l_whence; /* How to interpret l_start: SEEK_SET, SEEK_CUR, SEEK_END */ off_t l_start; /* Starting offset for lock */ off_t l_len; /* Number of bytes to lock */ pid_t l_pid; /* PID of process blocking our lock (F_GETLK only) */ ... };```### 参考链接https://juejin.im/editor/drafts/5b8658f16fb9a019d7476671复制代码