Linux系统上的文件锁主要分为协同锁(advisory lock)和强制锁(mandatory lock)。在Linux上使用的文件锁大部分为协同锁,而且使用强制锁的时候也要检查系统是否支持强制锁.
1)协同锁
协同锁要求参与操作的进程之间协同合作。假设进程“A”获得一个WRITE锁,并开始向文件中写入内容;此时,进程“B”并没有试图获取一个锁,它仍然可以打开文件并向文件中写入内容。在此过程中,进程“B”就是一个非合作进程。如果进程“B”试图获取一个锁,那么整个过程就是一个合作的过程,从而可以保证操作的“序列化”。
只有当参与操作的进程是协同合作的时候,协同锁才能发挥作用。协同锁有时也被称为“非强制”锁。有些程序利用诸如 FIlENAME.lock 的文件锁文件,然后简单地测试此类文件是否存在。这种方法显然不太好,因为当产生文件的进程被杀后,锁文件依然存在,这样文件也许会被永久锁住。
UUCP中把产生文件的进程号PID存入文件,但这样做仍然不保险,因为PID的利用是回收型的。这里是三个文件锁函数:
flock();
lockf();
fcntl();
flock()是从BSD中衍生出来的,但目前在大多数UNIX系统上都能找到,在单个主机上flock()简单有效,但它不能在NFS上工作。Perl中也有一个有点让人迷惑的flock()函数,但却是在perl内部实现的。
fcntl()是唯一的符合POSIX标准的文件锁实现,所以也是唯一可移植的。它也同时是最强大的文件锁--也是最难用的。在NFS文件系统上,fcntl()请求会被递交给叫rpc.lockd的守护进程,然后由它负责和主机端的lockd对话,和flock()
不同,fcntl()可以实现记录层上的封锁。
lockf()只是一个简化了的fcntl()文件锁接口。
无论你使用哪一种文件锁,请一定记住在锁生效之前用sync来更新你所有的文件输入/输出。
2)强制锁
强制锁不需要参与操作的进程之间保持协同合作。它利用内核来查检每个打开、读取、写入操作,从而保证在调用这些操作时不违反文件上的锁规则。关于强制锁的更多信息,可以在kernal.org上找到。
为了使能Linux中的强制锁功能,你需要在文件系统级别上打开它,同时在单个文件上打开它。其步骤是:
(1)挂载文件系统时使用“-omand”参数。
(2)对于要打开强制锁功能的文件lock_file,必须打开set-group-ID位,关闭group-execute位。(选择此方法的原因是,当你关闭group-execute时,设置set-group-ID就没有实际的意义了)。
检测系统是否支持强制锁的代码如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/wait.h>
volatile sig_atomic_t sigflag;
int pfd1[2], pfd2[2];
sigset_t newmask, oldmask, zeromask;
void err_doit(int errnoflag, const char *fmt, va_list ap)
{
int errno_save;
char buf[4096];
errno_save = errno;
vsprintf(buf, fmt, ap);
if (errnoflag)
sprintf(buf+strlen(buf), ": %s", strerror(errno_save));
strcat(buf, "/n");
fflush(stdout);
fputs(buf, stderr);
fflush(stderr);
return;
}
void err_sys(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, fmt, ap);
va_end(ap);
exit(1);
}
void sig_usr(int signo)
{
sigflag = 1;
return;
}
TELL_WAIT()
{
if (signal(SIGUSR1, sig_usr) == SIG_ERR) /*SIGUSR1 为用户自定义信号*/
err_sys("signal(SIGINT) error");
if (signal(SIGUSR2, sig_usr) == SIG_ERR)
err_sys("signal(SIGQUIT) error");
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGUSR1);
sigaddset(&newmask, SIGUSR2);
/*阻塞 SIGUSR1 和 SIGUSR2 并且保存当前信号掩码*/
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
}
void WAIT_PARENT(void)
{
while( sigflag == 0)
sigsuspend(&zeromask); /*suspend()取消了所有信号屏蔽,等待父进程发来信号*/
sigflag = 0;
/*恢复信号掩码*/
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
}
int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;
lock.l_type = type;
lock.l_start = offset;
lock.l_whence = whence;
lock.l_len = len;
return( fcntl(fd, cmd, &lock) );
}
void set_fl(int fd, int flags)
{
int val;
if ( (val = fcntl(fd, F_GETFL, 0)) < 0)
err_sys("fcntl F_GETFL error");
val |= flags; /*置标志*/
if (fcntl(fd, F_SETFL, val) < 0)
err_sys("fcntl F_SETFL error");
}
void err_ret(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, fmt, ap);
va_end(ap);
return;
}
int main(void)
{
int fd;
pid_t pid;
char buff[5];
struct stat statbuf;
if ( (fd = open("templock", O_RDWR | O_CREAT | O_TRUNC, 0644)) < 0)
err_sys("open error",buff);
if (write(fd, "abcdef", 6) != 6)
err_sys("write error");
/*打开 set-group-ID(s-gid),并关闭组执行权限*/
if (fstat(fd, &statbuf) < 0)
err_sys("fstat error");
if (fchmod(fd, (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0)
err_sys("fork error");
TELL_WAIT();
if ( (pid = fork()) < 0) {
err_sys("fork error");
} else if (pid > 0) { /*父进程*/
/*整个文件写锁*/
if (lock_reg(fd, F_SETLK, F_WRLCK, 0, SEEK_SET, 0) < 0)
err_sys("write_lock error");
kill(pid, SIGUSR1); /*给子进程发送信号告知锁完成*/
if (waitpid(pid, NULL, 0) < 0)
err_sys("waitpid error");
} else {
WAIT_PARENT(); /*等待父进程设置锁*/
set_fl(fd, O_NONBLOCK);
if (lock_reg(fd, F_SETLK, F_RDLCK, 0, SEEK_SET, 0) != -1)
err_sys("child: read_lock succeeded");
printf("read_lock of already-locked region return %d/n", errno);
if (lseek(fd, 0, SEEK_SET) == -1)
err_sys("lseek, error");
if (read(fd, buff, 2) < 0)
err_ret("read failed (mandatory locking wroks)");
else
printf("read OK (no mandatory locking), buff = %2.2s/n", buff);
}
exit(0);
}