Linux 提供了多种特性来实现文件锁定。
一. 创建锁文件
许多应用程序只需要能够针对某个资源创建一个锁文件即可,然后,其它程序就可以通过检查这个文件来判断它们自己是否能够被允许访问这个资源。这些锁文件通常被放置在一个特定位置,并带有一个与被控制资源相关的文件名。
为了创建一个用作锁指示器的文件,可以使用 open 系统调用,并带上 O_CREAT 和 O_EXCL 标志。这样能够以一个原子操作同时完成两项工作:确定文件不存在,然后创建它。程序退出时,需要用 unlink 系统调用删除该锁文件。
二. 区域锁定
用第一种方法来控制对诸如串口或不经常访问的文件之类的资源的独占式访问,是一个不错的选择,但它并不适用于访问大型的共享文件。此时,可以通过锁定文件区域的方法来解决这个问题。Linux 提供了至少两种方式来实现这一功能:fcntl 系统调用和 lockf 系统调用。
1. fcntl
#include <fcntl.h>
int fcntl(int fildes, int command, struct flock *flock_structure);
第三个 flock 结构依赖具体的实现,但它至少包含下述成员:
short l_type
short l_where
off_t l_start
off_t l_len
pid_t l_pid
l_type 的取值包括:
F_RDLCK 共享(读写)锁。只要任一进程拥有一把共享锁,那么就没有进程可以再获得该区域上的独占锁
F_UNLCK 解锁
F_WRLCK 独占(写)锁,只有一个进程可以在文件的任一特定区域拥有一把独占锁
l_where、l_start 和 l_len 定义了文件中的一个区域。l_where 的取值必须是 SEEK_SET、SEEK_CUR、SEEK_END 中的一个。
l_pid 用来记录持有锁的进程。
第二个参数 command 有 3 个用于文件锁定的命令选项:
F_GETLK 用于获取 fildes 打开的文件的锁信息,而不会尝试去锁定文件,如果文件已被锁定,l_pid 将被设置成持有锁的进程号
F_SETLK 用于对 fildes 指向的文件的某个区域加锁或解锁
F_SETLKW 作用与 F_GETLK 相同,但在无法获取锁时,这个调用将阻塞
当对文件区域加锁后,必须使用底层的 read 和 write 调用来访问文件中的数据,而不能使用更高级的 fread 和 fwrite 调用,因为后者会缓存。
2. lockf
#include <unistd.h>
int lockf(int fildes, int function, off_t size_to_lock);
function 参数的取值:
F_ULOCK 解锁
F_LOCK 设置独占锁
F_TLOCK 测试并设置独占锁
F_TEST 测试其它进程设置的锁
可以看到,lockf 的接口更简单,但是功能和灵活性都较差,因此不常用,而是作为 fcntl 的补充。
需要注意,无论是锁文件,还是区域锁定,都仅仅充当一个指示器的角色,程序间需要通过相互协作来使用它们,或者说,都是建议锁,而不是强制锁,它们并不会真正的阻止你读写文件中的数据。
文件锁的典型使用场景是 pid 文件,用于防止进程启动多个副本。只有获得 pid 文件写入权限(F_WRLCK)的进程才能正常启动,并把自身的 pid 写入该文件中。
但是看过代码后发现,Apache 和 Memcached 对于防止启动进程的多个副本,实现方式略有不同。
Apache 的实现方式是,查看 pid 文件记录的进程是否存在:
rv = ap_read_pid(pconf, ap_pid_name, &otherpid);
if (rv != APR_SUCCESS) {
......
} else {
have_pid_file = 1;
if (kill(otherpid, 0) == 0) {
running = 1;
status = apr_psprintf(pconf, "httpd (pid %"APR_PID_T_FMT") already running", otherpid);
}
}
Memcached 更弱,只要 pid 文件存在,新的进程就无法启动,只能手动删除该 pid 文件然后再重新启动。