linux之fcntlflock建议锁的操作

前言

当我们要进行多进程编程的时候,经常存在多个进程需要访问同一个文件的情况,因此会产生进程间访问不一致的问题,那么我们可以用到fcntl函数,我们可以用它来对文件或者文件的一部分进行上锁。

   #include <unistd.h>#include <fcntl.h>int fcntl(int fd, int cmd, ... /* arg */ );

fcntl 可以施加建议性锁,也可以施加强制锁。同时还能对文件的某一记录进行上锁,也就是记录锁。
所谓建议性锁,就是说它不具备强制性,只是作为程序员之间的约定,如果你愿意,仍然可以直接去对一个上锁的文件进行操作。

fcntl

这里我们只讲述加建议性锁,即arg为struct flock指针类型的时候,如下。

       int fcntl(int fd, int cmd, struct flock*);

从fcntl的函数声明可以看到,它的参数是可变的。

第一个参数fd:

要操作的文件描述符,

第二个参数cmd:

要操作的指令类型,我们只考虑在第三个参数为flock结构体指针的情况下,cmd有三种取值情况:
F_GETLK 取得文件锁定的状态。
F_SETLK 设置文件锁定的状态。此时flcok 结构的l_type 值必须是F_RDLCK、F_WRLCK或F_UNLCK。如果无法建立锁定,则返回-1,错误代码为EACCES 或EAGAIN。
F_SETLKW F_SETLK 作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止。若在等待锁定的过程中被信号中断时,会立即返回-1,错误代码为EINTR。

第三个参数struct flock*:

flock结构体定义如下

struct flock
{short int l_type;short int l_whence;off_t l_start;off_t l_len;pid_t l_pid;
};

l_type 有三种状态:

F_RDLCK 建立读锁
F_WRLCK 建立写锁
F_UNLCK 删除之前建立的锁

l_whence 也有三种方式:

SEEK_SET 以文件开头为锁定的起始位置。
SEEK_CUR 以目前文件读写位置为锁定的起始位置
SEEK_END 以文件结尾为锁定的起始位置。

**l_start 表示相对l_whence位置的偏移量,两者一起确定锁定区域的开始位置。
l_len表示锁定区域的长度,若为0则表示整个文件的长度,即不管在后面增加多少数据都在锁的范围内。
返回值 成功返回依赖于cmd的值,若有错误则返回-1,错误原因存于errno.**

测试过程中发现的问题!!!

  1. 用fcntl获取锁的时候(F_GETLK),struct flock*参数的l_type取值必须为(F_RDLCK|F_WRLCK|F_UNLCK)中的一个,否则fcntl会执行失败,这个坑了我半天的时间,因为以为struct flock*只是作为接受锁信息的载体,没想到其type也必须要赋值才行。测试证明,l_type的取值跟获取锁的结果没有任何关系,其赋值的意义仅在于赋值合法而已。
  2. 读锁作为共享锁,是可以存在多个的,所以在A进程里设置读锁后,在B进程里是获取不到的。
  3. 进程A设置的锁对进程A是不可见的,也就是说进程A无法GET到自己获得的锁。

测试程序

/*fcntl_write.c测试文件写入锁主函数部分*/
#include <unistd.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>/*lock_set函数*/
void lock_set(int fd, int type)
{struct flock lock;lock.l_whence = SEEK_SET;//赋值lock结构体lock.l_start = 0;lock.l_len =0;lock.l_type = type;/*根据不同的type值给文件上锁或解锁*/if((fcntl(fd, F_SETLK, &lock)) == 0){if( lock.l_type == F_RDLCK )printf("read lock set by %d\n",getpid());else if( lock.l_type == F_WRLCK )printf("write lock set by %d\n",getpid());else if( lock.l_type == F_UNLCK )printf("release lock by %d\n",getpid());}else{/*判断文件是否可以上锁*/fcntl(fd, F_GETLK,&lock);/*判断文件不能上锁的原因*/if(lock.l_type == F_UNLCK)printf("no lock by %d\n",lock.l_pid);/*/该文件已有写入锁*/if( lock.l_type == F_RDLCK )printf("read lock already set by %d\n",lock.l_pid);/*该文件已有读取锁*/else if( lock.l_type == F_WRLCK )printf("write lock already set by %d\n",lock.l_pid);}
}void lock_get(int fd)
{struct flock lock;lock.l_whence = SEEK_SET;//赋值lock结构体lock.l_start = 0;lock.l_len =0;lock.l_type = F_RDLCK;/*判断文件是否可以上锁*/if(0 > fcntl(fd, F_GETLK,&lock)){printf("get lock failure by %d\n",getpid());}/*判断文件不能上锁的原因*/if(lock.l_type == F_UNLCK)printf("no lock by %d\n",lock.l_pid);/*/该文件已有写入锁*/else if( lock.l_type == F_RDLCK )printf("read lock already set by %d\n",lock.l_pid);/*该文件已有读取锁*/else if( lock.l_type == F_WRLCK )printf("write lock already set by %d\n",lock.l_pid);
}int main()
{int fd;/*首先打开文件*/fd=open("hello",O_RDWR | O_CREAT, 0666);if(fd < 0){perror("open");exit(1);}while(1){char str[4];int type;scanf("%s", str);switch(str[0]){//write lockcase 'w':type = F_WRLCK;break;//read lockcase 'r':type = F_RDLCK;break;//unlockcase 'u':type = F_UNLCK;break;//getlockcase 'g':type = 0;break;}if(type == 0)lock_get(fd);elselock_set(fd, type);}close(fd);return 0;
}