前面有讲过竞争冒险的问题,如果有多个进程对文件进行I/O操作,容易产生竞争状态、导致文件中的内容与预想的不一致的问题,由此引入文件锁。
内核提供的锁机制用于对共享资源的访问进行保护,而文件锁是一种应用于文件的锁机制,当多个进程同时操作同一文件时,对文件上锁,来避免多个进程同时操作同一文件时产生竞争状态。
文件锁可以分为建议性锁和强制性锁两种:
建议性锁本质上是一种协议,程序访问文件之前,先对文件上锁,上锁成功之后再访问文件,这是建议性锁的一种用法。顾名思义,在没有对文件上锁的情况下直接访问文件,也是可以访问的。
强制性锁比较好理解,它是一种强制性的要求,如果进程对文件上了强制性锁,其它的进程在没有获取到文件锁的情况下是无法对文件进行访问的。强制性锁会让内核检查每一个I/O操作验证调用进程是否是该文件锁的拥有者,否则将无法访问文件。当一个文件被上锁进行写入操作的时候,内核将阻止其它进程对其进行读写操作。采取强制性锁对性能的影响很大,每次进行读写操作都必须检查文件锁。
flock用于对文件加锁或者解锁,但是只能产生建议性锁,并且同一个文件不会同时具有共享锁和互斥锁。
1.头文件
#include <sys/file.h>
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 <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/file.h> #include <signal.h> 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 <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/file.h> #include <signal.h> #include <string.h> 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、当一个文件描述符被复制时,会共用同一个文件锁。