7. 高级文件操作:文件锁
(1)文件锁分类
分类依据 |
类型 |
说明 |
按功能分 |
共享读锁 |
文件描述符必须读打开 一个进程上了读锁,共它进程也可以上读锁进行读取 |
独占写锁 |
文件描述符必须写打开 一个进程上了写锁,其它进程就不能上写锁和读锁进行读写操作 |
|
按类型分 |
建议锁 |
要求上锁文件的进程都要检测是否有锁的存在,并尊重巳有的锁。这也是Linux默认的锁类型。 |
强制锁 |
由内核和系统执行的锁。 |
(2)fcntl函数
头文件 |
#include<unistd.h> |
函数 |
int fcntl(int fd, int cmd, struct flock* lock); |
返回值 |
若成功返回新文件描述符,出错返回-1 |
功能 |
若成功则依赖于cmd,出错为-1。 |
参数 |
cmd:F_SETLK、F_GETLK和F_SETLKW(阻塞版的fcntl函数) struct flock{ short l_type; //F_RDLCK,F_WRLCK,or F_UNLCK off_t l_start; //相对于l_whence的偏移量 short l_whence; //SEEK_SET,SEEK_CUR,or SEEK_END off_t l_len; //长度(单位字节),0表示锁到文件尾 pid_t l_pid; //使用F_GETLK时有效,返回持有锁的进程 } |
备注 |
①l_type:表示锁的类型,F_RDLCK为共享读锁,F_WRLCK为独占写锁,F_UNLCK解锁。 ②l_whence、l_start:要加锁或解锁的区域起始地址,则这两者共同决定。注意,区域的起始地址可以在文件尾端或越过尾端开始,但不能从文件起始位置之前开始。 ③l_len:表示区域的长度。如果为0,则表示锁的区域从其起点(由l_start和l_whence共同决定)开始,直到最大可能位置为止。也就是不管整个文件。为了锁整个文件,通常的作法是l_whence为SEEK_SET,l_start和l_len都设为0 ④fcntl函数可以作用于建议锁也就可以作用于强制锁。 |
(3)建议锁和强制锁
①建议性锁:要求每个使用文件的进程都要主动检查该文件是否有锁存在(可以通过fcntl函数通过F_GETLK来检查)。如果有,则合作的进程必须主动加锁,以防止对文件的破坏。如果一个进程加锁,而其它的进程不加锁,则这个锁无法约束不加锁进程对文件的进行读写操作的行为。因为锁只是建议性存在,并不强制执行。为了让这个锁起作用,要求其它进程要尊重这个锁的存在,主动去加锁。Linux默认是采用建议性锁,它们依靠程序员遵守这个约定。(要么合作的进程都加锁,要么都不加锁,否则锁的作用无法发挥出来!)
②强制性锁:当文件被上锁来进行读写操作时,在锁定该文件的进程释放该锁之前,内核会强制阻止任何对该文件的读或写违规访问,每次读或写访问都得检查锁是否存在。也就是强制性锁机制,让锁变得名副其实,真正达到了锁的效果。而不是像建议性锁机制,当其它进程不加锁时,它只是个一只纸老虎!
(4)开启强制性锁
①重新挂载文件系统所在的分区:mount -o remount,mand /dev/sda5 /
②设置文件的组ID位,关闭组执行位:chmod g+s,g-x file.txt
【编程实验】多进程同时写同一文件
//io.h
#ifndef __IO_H__ #define __IO_H__ #include <sys/types.h> extern void copy(int fdin, int fdout);//文件复制 extern void set_fl(int fd, int flag); //设置文件状态标志 extern void clr_fl(int fd, int flag); //取消文件状态标志 //文件锁 extern int lock_reg(int fd, int cmd, short type, off_t offset, short whence, off_t length); #define READ_LOCKW(fd, offset, whence, length) lock_reg(fd, F_SETLKW, F_RDLCK, offset, whence, length) #define READ_LOCK(fd, offset, whence, length) lock_reg(fd, F_SETLK, F_RDLCK, offset, whence, length) #define WRITE_LOCKW(fd, offset, whence, length) lock_reg(fd, F_SETLKW, F_WRLCK, offset, whence, length) #define WRITE_LOCK(fd, offset, whence, length) lock_reg(fd, F_SETLK, F_WRLCK, offset, whence, length) #define UNLOCKW(fd, offset, whence, length) lock_reg(fd, F_SETLKW, F_UNLCK, offset, whence, length) #define UNLOCK(fd, offset, whence, length) lock_reg(fd, F_SETLK, F_UNLCK, offset, whence, length) #endif
//io.c
#include "io.h" #include <unistd.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> //编译命令:gcc -o obj/io.o -Iinclude -c src/io.c #define BUFFER_LEN 1024 //与分区文件块大小一致。可以通过 //tune2fs -l /dev/sda1命令查看 void copy(int fdin, int fdout) { char buffer[BUFFER_LEN]; ssize_t size; //保证从文件开始处复制 lseek(fdin, 0L, SEEK_SET); lseek(fdout, 0L, SEEK_SET); while((size = read(fdin, buffer, BUFFER_LEN)) > 0){ if(write(fdout, buffer, size) != size) { fprintf(stderr, "write error: %s ", strerror(errno)); exit(1); } } if (size < 0 ) { fprintf(stderr, "read error: %s ",strerror(errno)); exit(1); //return 1; } } void set_fl(int fd, int flag) //设置文件状态标志 { //获取原来的文件状态标志 int val = fcntl(fd, F_GETFL); //增加新的文件状态标志 val |= flag; //重新设置文件状态标志 if(fcntl(fd, F_SETFL, val) < 0) { perror("fcntl error"); } } void clr_fl(int fd, int flag) //取消文件状态标志 { //获取原来的文件状态标志 int val = fcntl(fd, F_GETFL, val); //清除指定的文件状态标志(置0) val &= ~flag; //重新设置文件状态标志 if(fcntl(fd, F_SETFL, val) < 0 ) { perror("fcntl error"); } } //文件锁 int lock_reg(int fd, int cmd, short type, off_t offset, short whence, off_t length) { struct flock flock; flock.l_type = type; flock.l_start = offset; flock.l_whence = whence; flock.l_len = length; //flock.l_pid 加锁的进程号,会由操作系统自己填入,当F_GETLK时可以获取到 if(fcntl(fd, cmd, &flock) < 0){ perror("fcntl error"); return 0; } return 1; }
//lock_write.c
#include "io.h" #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <string.h> //演示多进程对加独占写锁的文件同时写入的问题。 int main(int argc, char* argv[]) { if(argc < 4){ printf("usage: %s content file locktype ", argv[0]); exit(1); } ssize_t size = strlen(argv[1]) * sizeof(char); int fd = open(argv[2], O_WRONLY | O_CREAT, 0777); if(fd < 0){ perror("open error"); exit(1); } //查看文件被哪个进程锁住: struct flock flock; flock.l_start=0; flock.l_type=F_RDLCK; flock.l_len=0; flock.l_whence=SEEK_SET; if(fcntl(fd, F_GETLK, &flock) < 0 ) { perror("fcntl error"); exit(1); } printf("current pid: %d,is locked by another process[pid:%d] ",getpid(),flock.l_pid); //sleep(5); //加文件锁(注意Linux默认是建议锁,为了防止同多进程同时写文件(O_WRONLY),要求其它 //合作的进程也要同时加O_WRONLY或O_RDONLY。如果其它进程如果不加锁,也是可以直接读 //或写该文件的。因为它是建议锁,为体现锁的功能,其他进程必须遵守加锁的约定,即 //在遵守加锁的约定后,第二个进程想要对文件加锁必须要等到第一个进程释放文件锁后, //才可以获取锁并进行加锁操作。 if(!strcmp("lock", argv[3])){ WRITE_LOCKW(fd, 0, SEEK_SET, 0); //独占写锁 printf("lock success "); } //写入content的内容,为了演示交替写入,将字符一个个写入 char* p = argv[1]; int i = 0; for(i=0; i<size; i++){ if(write(fd, p+i, 1) != 1){ perror("write error"); exit(1); } printf("%d success write one character[-%c-]! ", getpid(), *(p+i)); sleep(1); } //释放锁 if(!strcmp("lock", argv[3])){ UNLOCKW(fd, 0, SEEK_SET,0); printf("unlock success "); printf("unlock pid: %d ", getpid()); } close(fd); return 0; }
//start.sh
#因为是建议锁,所以两个进程必须同时加锁,锁的功能才能起作用 bin/lock_write aaaaaa lock.txt lock & #加共享读写锁 bin/lock_write AAAAAA lock.txt lock & #加共享读写锁 #如果进程1加锁,进程2不加锁。则锁的作用将不起作用,这就是 #建议性锁的问题,即要求如果锁要起作用,则大家都要加锁。 #bin/lock_write aaaaaa lock.txt lock & #加共享读写锁 #bin/lock_write AAAAAA lock.txt nolock & #注意,这里不加锁,上一个进程的锁将无法约束这里写的行为。