前面有讲过竞争冒险的问题,如果有多个进程对文件进行I/O操作,容易产生竞争状态、导致文件中的内容与预想的不一致的问题,由此引入文件锁。
内核提供的锁机制用于对共享资源的访问进行保护,而文件锁是一种应用于文件的锁机制,当多个进程同时操作同一文件时,对文件上锁,来避免多个进程同时操作同一文件时产生竞争状态。
文件锁可以分为建议性锁和强制性锁两种:
建议性锁本质上是一种协议,程序访问文件之前,先对文件上锁,上锁成功之后再访问文件,这是建议性锁的一种用法。顾名思义,在没有对文件上锁的情况下直接访问文件,也是可以访问的。
强制性锁比较好理解,它是一种强制性的要求,如果进程对文件上了强制性锁,其它的进程在没有获取到文件锁的情况下是无法对文件进行访问的。强制性锁会让内核检查每一个I/O操作验证调用进程是否是该文件锁的拥有者,否则将无法访问文件。当一个文件被上锁进行写入操作的时候,内核将阻止其它进程对其进行读写操作。采取强制性锁对性能的影响很大,每次进行读写操作都必须检查文件锁。
flock用于对文件加锁或者解锁,但是只能产生建议性锁,并且同一个文件不会同时具有共享锁和互斥锁。
1.头文件
#include
2.函数原型
int flock(int fd, int operation);
3.参数
fd:表示需要加锁文件的文件描述符。
operation:指定了操作方式,可以设置为以下值的其中一个:
LOCK_SH:在fd引用的文件上放置一把共享锁。所谓共享,指的便是多个进程可以拥有对同一个文件的共享锁,该共享锁可被多个进程同时拥有。
LOCK_EX:在fd引用的文件上放置一把排它锁(或叫互斥锁)。所谓互斥,指的便是互斥锁只能同时被一个进程所拥有。
LOCK_UN:解除文件锁定状态,解锁、释放锁。
除了以上三个标志外,还有一个标志:
LOCK_NB:表示以非阻塞方式获取锁。默认情况下,调用flock无法获取到文件锁时会阻塞、直到其它进程释放锁为止,如果不想让程序被阻塞,可以指LOCK_NB标志,如果无法获取到锁,应立刻返回(错误返回,并将errno设置为EWOULDBLOCK),通常与LOCK_SH或LOCK_EX一起使用,通过位或运算符组合在一起。
4.返回值
成功将返回 0,失败返回-1,并会设置errno。
5.示例
#include
#include
#include
#include
#include
#include
#include
#include
static int fd;
char *name;
static void sig_handler(int sig){
if (sig != SIGUSR1)
return;
flock(fd,LOCK_UN);
close(fd);
printf("%s is unlock\n",name);
exit(0);
}
int main(int argc, char *argv[])
{
if (argc != 2) {
printf("usage:flocktest filename\n");
return -1;
}
fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, 0777);
if (fd < 0) {
perror("open file");
return -1;
}
if (flock(fd, LOCK_EX | LOCK_NB) < 0) {
perror("flock file");
close(fd);
return -1;
}
name = argv[1];
printf("flock file %s succeed\n", name);
signal(SIGUSR1, sig_handler);
while (1)
sleep(1);
return 0;
}
以上先通过传参获取要打开的文件,并对文件进行加锁操作,然后设置当接收到SIGUSR1信号时,解锁并结束进程,未收到信号则一直循环等待。
另一个进程:
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
int pid, fd;
char *name;
char buf[30] = "flock2 test";
char buf2[30];
if (argc != 3) {
printf("usage:flocktest filename pid\n");
return -1;
}
pid = atoi(argv[2]);
fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, 0777);
if (fd < 0) {
perror("open file");
return -1;
}
if (flock(fd, LOCK_EX | LOCK_NB) < 0) {
perror("第一次加锁失败");
if (write(fd, buf, strlen(buf)) < 0) {
perror("flock2 write");
exit(-1);
}
if (lseek(fd, 0, SEEK_SET) < 0) {
perror("lseek error");
exit(-1);
}
if (read(fd, buf2,strlen(buf)) < 0) {
perror("read error");
exit(-1);
}
if (strcmp(buf,buf2) == 0) {
printf("读写相同\n");
}
kill(pid,SIGUSR1);
sleep(1);
if (flock(fd, LOCK_EX | LOCK_NB) < 0)
perror("第2次加锁失败");
else
printf("第2次加锁成功\n");
}
return 0;
}
在此程序中,同样对传参指定的文件进行加锁操作,当加锁失败后,先写入特定内容,然后读取对比,可以看到下面实际运行时,打印信息“读写相同”,说明虽然有锁,但是仍然可以进行IO操作。而后通过kill向传参指定的上一个进程的pid发生SIGUSR1信号,使flock1进程触发解锁以及退出进程操作。而后flock2再次进行加锁,可以看到打印信息中“第2次加锁成功“。
6)编译运行并查看测试结果
$ ./flock1 test & //flock1是第一个例程的可执行程序,test是任意文件
[1] 5303 //后台运行第一个程序时,返回进程pid
flock file test succeed
$ ./flock2 test 5303 //flock2是第二个例程,test是指定的文件,5303是第一个程序的pid
第一次加锁失败:Resource temporarily unavailable
读写相同
test is unlock
第2次加锁成功
[1]+ 已完成 ./flock1 test //后台运行的第一个程序结束退出
有几点需要注意:
1、同一进程对文件多次加锁不会导致死锁。
2、文件关闭的时候,会自动解锁。
3、一个进程不可以对另一个进程持有的文件锁进行解锁。
4、由fork创建的子进程不会继承父进程所创建的锁。
5、当一个文件描述符被复制时,会共用同一个文件锁。
|